上周我发布了一篇博文有多少种硬币组合——找出独特子数组之和,是关于有多少种硬币组合的算法题的解法。虽然算法自己可以给出一个正确答案,但是仔细想来,我却没办法给出一个简单直接的解释为何这样跑能够奏效。好的算法应该是可以以一种通俗易懂的方法教给别人的,若是连作出这道算法题的人都没法解释清楚,那即使这个算法可以跑起来,也不能算好吧。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