上週我發佈了一篇博文有多少種硬幣組合——找出獨特子數組之和,是關於有多少種硬幣組合的算法題的解法。雖然算法自己可以給出一個正確答案,但是仔細想來,我卻沒辦法給出一個簡單直接的解釋爲何這樣跑能夠奏效。好的算法應該是可以以一種通俗易懂的方法教給別人的,若是連作出這道算法題的人都沒法解釋清楚,那即使這個算法可以跑起來,也不能算好吧。javascript
就這個問題,一位高人給出了一個更好的解決方式,通俗易懂。聽完以後,我真是不得不服功力之高深吶~java
若是你有一個硬幣數組和一個表明其數量的數組,如何獲得一共有多少種不一樣組合所得的和?算法
好比:硬幣數組[10, 50, 100]
,和表明其數量的數組[1, 2, 1]
就表示這裏一共有三種硬幣,1枚10分,2枚50分和1枚100分硬幣。那麼其可能的排列組合則包括數組
則,不重複的值則包括加黑的部分,一共是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後面加上一枚新的B
A + B
除此以外,以前的A也應該在裏面
A + B
A
再加上新增長的B,則變成了:
A + B
A
B
這時候加第三枚了,咱們在以前的這三種狀況下都加上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 = 3
3 * 2 + 1 = 7
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