有多少種硬幣組合,更優解法

寫在前面的自我反省 v2

上週我發佈了一篇博文有多少種硬幣組合——找出獨特子數組之和,是關於有多少種硬幣組合的算法題的解法。雖然算法自己可以給出一個正確答案,但是仔細想來,我卻沒辦法給出一個簡單直接的解釋爲何這樣跑能夠奏效。好的算法應該是可以以一種通俗易懂的方法教給別人的,若是連作出這道算法題的人都沒法解釋清楚,那即使這個算法可以跑起來,也不能算好吧。javascript

就這個問題,一位高人給出了一個更好的解決方式,通俗易懂。聽完以後,我真是不得不服功力之高深吶~java

問題和複製粘貼的分析

若是你有一個硬幣數組和一個表明其數量的數組,如何獲得一共有多少種不一樣組合所得的和?算法

好比:硬幣數組[10, 50, 100],和表明其數量的數組[1, 2, 1]就表示這裏一共有三種硬幣,1枚10分,2枚50分和1枚100分硬幣。那麼其可能的排列組合則包括數組

  1. 10 = 10
  2. 50 = 50
  3. 100 = 100
  4. 10 + 50 = 60
  5. 10 + 100 = 110
  6. 50 + 50 = 100
  7. 50 + 100 = 150
  8. 10 + 50 + 50 = 110
  9. 10 + 50 + 100 = 160
  10. 50 + 50 + 100 = 200
  11. 10 + 50 + 50 + 100 = 210

則,不重複的值則包括加黑的部分,一共是9個。數據結構

接下來是正經分析

第一步:將全部硬幣組成一個數組

首先,上一篇博文裏的第一步和第二步是正確的,咱們確實須要將全部硬幣組合起來,組成一個新的數組coinList,其中包含了全部的硬幣。則這部分的代碼仍是繼續使用。測試

代碼以下:code

/**
 * Count number of 
 * @param {Array<Number>} coins array contains coins with different values
 * @param {Array<Number>} counts array contains corresponding counts of different coins
 * @returns {Number} The count of distinct sums
 */
function countDistinctSum(coins, counts) {
    if(coins.length !== counts.length) {
        return -1;
    }

    const results = {};
    const coinList = [];

    for(let i = 0; i < coins.length; i++){
        for(let j = 0; j < counts[i]; j++) {
            coinList.push(coins[i]);
        }
    }

    // where the beauty of algorithm shows

    return Object.keys(results).length - 1; // Remove the empty 0 coin element
}

第二步:瞭解全部的組合是如何計算獲得的

咱們一步一步來看,全部的組合是如何的出來的。假如,如今咱們只有一個硬幣,1分。則可能性只有1種,那就是[1]ip

如今咱們增長一個硬幣,2分。則可能性則變成了[1, 2, 1+2],3種。element

若是咱們再加一個硬幣,3分呢?則可能性又變成了[1, 2, 3, 1+2, 1+3,2+3,1+2+3],7種。rem

仔細看,當硬幣的數量一個一個地增長,可能的組合數量也相應地有規律地再增長。什麼規律???好像看不是很清楚。那麼咱們換一種方法來表示呢?

若是將硬幣表示爲A, B, C。

一枚硬幣:A

一枚硬幣的時候,是:
A

兩枚硬幣:A、B

兩枚硬幣的時候呢?那咱們直接在以前的A後面加上一枚新的B

A + B

除此以外,以前的A也應該在裏面

A + B

A

再加上新增長的B,則變成了:

A + B

A

B

三枚硬幣:A、B、C

這時候加第三枚了,咱們在以前的這三種狀況下都加上C,則變成了

A + B + C

A + C

B + C

而以前的那三種組合是還存在的,因此整個數組變成了

A + B + C

A + C

B + C

A + B

A

B

最後加上新增長的C,最終獲得了

A + B + C

A + C

B + C

A + B

A

B

C

這下是否更清楚了?每當一個新的硬幣被加進數組之中時,組合的數量始終是以前的兩倍多一個。好比:

  • 1枚硬幣時,組合數量是1
  • 2枚硬幣時,組合數量是1 * 2 + 1 = 3
  • 3枚硬幣時,組合數量是3 * 2 + 1 = 7
  • 4枚硬幣時,組合數量是7 * 2 + 1 = 15

......

以此類推

第三步:只儲存非重複的值

在計算過程當中,不免會遇到許多重複的值。好比兩枚硬幣都是10分的時候,計算出來的組合是[10, 10, 20]。但其實咱們不須要兩個10,而只須要[10, 20]就能夠了。這個時候咱們須要用到Set這種數據結構來儲存數據。由於set裏面的元素都是非重複的。

好比,一組硬幣[10, 50, 50]。處理第一枚硬幣的時候,Set裏有[10]

處理第二枚硬幣時,對這個Set裏的全部元素提取出來而且加上新的硬幣值,10 + 50獲得了60,並添加獲得的和與新添加的這枚硬幣值進入進入Set裏,獲得了[10, 50, 60].

處理第三枚硬幣時,仍是繼續提取出這個Set裏的全部元素,而且加上新的硬幣值。因而:

  • 10 + 50 = 60
  • 50 + 50 = 100
  • 60 + 50 = 110

將這三個新的和加入Set,去掉重複值以後獲得了[10, 50, 60, 100, 110]。而後再把第三枚硬幣自己的值,50,也添加進去。但由於50已經存在了,則Set仍是[10, 50, 60, 100, 110]

一直重複循環到最後,咱們將獲得全部非重複的和。問題至此也被解決了~

大功告成

完整代碼

/*
 * Count number of
 * @param {Array<Number>} coins array contains coins with different values
 * @param {Array<Number>} counts array contains corresponding counts of different coins
 * @returns {Number} The count of distinct sums
 */
function countDistinctSum(coins, counts) {
    if(coins.length !== counts.length) {
        return -1;
    }

    const coinList = [];
    for(let i = 0; i < coins.length; i++){
        for(let j = 0; j < counts[i]; j++) {
            coinList.push(coins[i]);
        }
    }
    // Create a new set
    const results = new Set();
    for(let i = 0; i < coinList.length; i++){
        // tempSum is used to store the temporary sum of every element in the set and new coin value
        const tempSum = [];
        for (let it = results.values(), val= null; val = it.next().value; ) {
            tempSum.push(val + coinList[i]);
        }

        // add every sum in tempSum to the set and the set will do automatic duplication removal.
        tempSum.forEach(n => {
            results.add(n);
        });

        // add the new coin value into the set
        results.add(coinList[i]);
    }

    return results.size;
}

測試:

const a = [10, 50, 100];
const b = [1, 2, 1];

console.log('Result:', countDistinctSum(a, b)) // Result: 9
相關文章
相關標籤/搜索