资讯专栏INFORMATION COLUMN

有多少种硬币组合,更优解法

williamwen1986 / 973人阅读

摘要:写在前面的自我检讨上周我发布了一篇博文有多少种硬币组合找出独特子数组之和,是关于有多少种硬币组合的算法题的解法。假如,现在我们只有一个硬币,分。则可能性只有种,那就是。

写在前面的自我检讨 v2

上周我发布了一篇博文有多少种硬币组合——找出独特子数组之和,是关于有多少种硬币组合的算法题的解法。虽然算法本身能够给出一个正确答案,可是仔细想来,我却没办法给出一个简单直接的解释为什么这样跑可以奏效。好的算法应该是能够以一种通俗易懂的方法教给别人的,如果连做出这道算法题的人都无法解释清楚,那即便这个算法能够跑起来,也不能算好吧。

就这个问题,一位高人给出了一个更好的解决方式,通俗易懂。听完之后,我真是不得不服功力之高深呐~

问题和复制粘贴的分析

如果你有一个硬币数组和一个代表其数量的数组,如何得到一共有多少种不同组合所得的和?

比如:硬币数组[10, 50, 100],和代表其数量的数组[1, 2, 1]就表示这里一共有三种硬币,1枚10分,2枚50分和1枚100分硬币。那么其可能的排列组合则包括

10 = 10

50 = 50

100 = 100

10 + 50 = 60

10 + 100 = 110

50 + 50 = 100

50 + 100 = 150

10 + 50 + 50 = 110

10 + 50 + 100 = 160

50 + 50 + 100 = 200

10 + 50 + 50 + 100 = 210

则,不重复的值则包括加黑的部分,一共是9个。

接下来是正经分析 第一步:将所有硬币组成一个数组

首先,上一篇博文里的第一步和第二步是正确的,我们确实需要将所有硬币组合起来,组成一个新的数组coinList,其中包含了所有的硬币。则这部分的代码还是继续使用。

代码如下:

/**
 * Count number of 
 * @param {Array} coins array contains coins with different values
 * @param {Array} 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]

现在我们增加一个硬币,2分。则可能性则变成了[1, 2, 1+2],3种。

如果我们再加一个硬币,3分呢?则可能性又变成了[1, 2, 3, 1+2, 1+3,2+3,1+2+3],7种。

仔细看,当硬币的数量一个一个地增加,可能的组合数量也相应地有规律地再增加。什么规律???好像看不是很清楚。那么我们换一种方法来表示呢?

如果将硬币表示为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} coins array contains coins with different values
 * @param {Array} 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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/99057.html

相关文章

  • 多少硬币组合——找出独特子数组之和

    摘要:另外,我们还需要将所有硬币组合起来,组成一个新的数组,其中包含了所有的硬币。比如硬币数组,和代表其数量的数组,组合成。 写在前面的自我检讨 这道题的解法,刚开始我自己做的并不算是一个很好的解法,只能说题目是做出来了,但过程中的计算有大量的重复计算,导致函数复杂度直逼O(n^n)。查阅资料之后便有了一个改良版。感谢这篇文章Find all distinct subset (or subs...

    xiaoqibTn 评论0 收藏0
  • 函数范式入门(惰性求值与函数式状态)

    摘要:纯函数式状态随机数生成器很明显,原有的函数不是引用透明的,这意味着它难以被测试组合并行化。售货机在输出糖果时忽略所有输入本章知识点惰性求值函数式状态 第二节 惰性求值与函数式状态 在下面的代码中我们对List数据进行了一些处理 List(1,2,3,4).map(_ + 10).filter(_ % 2 == 0).map(_ * 3) 考虑一下这段程序是如何求值的,如果我们跟踪一下...

    Jrain 评论0 收藏0
  • 现金找零方式的总数(sicp)

    问题:现有现金a,并且有n种面额的零钱,问,共有多少种找零方式。问题细化:现有现金1元,并且有50分,25分,10分,5分,1分五种面额,用这5种零钱组成1元,共有多少种方式? 如果把n种零钱按照某种顺序排列(如50分,25分,10分,5分,1分,不一定升序或降序,也可以乱序),那么问题可以转化为:现金a用除第一种零钱之外其他面额的找零方式数目加上现金a-d用所有面额的找零方式数目,其中d为第一...

    pf_miles 评论0 收藏0
  • 理解CKB的Cell模型

    摘要:交易依然表示状态的变化迁移。这样一个模型的特点是状态是第一性的所有者是状态的属性,每一份状态只有一个所有者状态不断的被销毁和创建。值得指出的是,的编程模型之所以强大,更多是因为它有状态,而不是因为它的有限图灵完备。 在设计 CKB 的时候,我们想要解决三个方面的问题: 状态爆炸引起的公地悲剧及去中心化的丧失;计算和验证耦合在了一起使得无论是计算还是验证都失去了灵活性,难以扩展;交易与价...

    henry14 评论0 收藏0

发表评论

0条评论

williamwen1986

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<