资讯专栏INFORMATION COLUMN

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

xiaoqibTn / 2136人阅读

摘要:另外,我们还需要将所有硬币组合起来,组成一个新的数组,其中包含了所有的硬币。比如硬币数组,和代表其数量的数组,组合成。

写在前面的自我检讨

这道题的解法,刚开始我自己做的并不算是一个很好的解法,只能说题目是做出来了,但过程中的计算有大量的重复计算,导致函数复杂度直逼O(n^n)。查阅资料之后便有了一个改良版。感谢这篇文章Find all distinct subset (or subsequence) sums of an array!

背景

最近因为一些原因,做了几道“简单”的算法题。今天要讲的便是其中的一道题:如果你有一个硬币数组和一个代表其数量的数组,如何得到一共有多少种不同组合所得的和?

分析

比如:硬币数组[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个。

而我们现在的问题便是,输入两个数组:硬币数组和代表其数量的数组,得到一个整数的返回值。

实际操作 第一步 定义输入与输出

根据分析,函数的输入与输出应该规定如下。

/**
 * 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) {
    // Implementation
}
第二步 初始化变量和定义函数结构

首先,我们应该先做一个检查,如果coins的长度跟counts的长度并不相等的话,则函数不应该继续处理,而是应该返回一个错误值。暂且定为-1吧。

然后,我们采用key value pair的形式来储存不同和(sum)的数量。所以我们必须有一个名叫result的对象。当然,其中并没有任何的key。在函数运行必要的计算之后,再返回result的key的数量作为最后的答案。

另外,我们还需要将所有硬币组合起来,组成一个新的数组coinList,其中包含了所有的硬币。比如:硬币数组[10, 50, 100],和代表其数量的数组[1, 2, 1],组合成[10, 50, 50, 100]。这一部分用两个for loop轻松搞定。

代码如下:

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]);
        }
    }

    processList(coinList, coinList.length, 0, 0, results);

    return Object.keys(results).length - 1; // Remove the empty 0 coin element
}
第三步 处理coinList

当我们得到一个coinList之后,接下来我们便要处理这个数组,将其内部的元素全都排列一遍。比如,[10, 50, 100]就有以下排列方法。

[10]

[10, 50]

[10, 100]

[10, 50, 100]

[50]

[50, 100]

[100]

这时,我便考虑使用递归法(recursive method)来解决这个问题。

函数接受两个输入

results用来储存已经有的sum

n用来储存硬币数组的长度

sum用来储存临时已经叠加的和

currentIndex用来记录当前遍历到的索引

coinList用来输入当下数组里的硬币元素。

设置出口条件

currentIndex大于coinList的长度时,返回。

currentIndex等于coinList的长度时,调用addToResults函数(下一步讲解)然后返回。

递归函数

processList()会被递归两次,这两者之间的带入参数区别只有sum的不同而已

第一个processList()将带入sum加上当下的coin值,达到计算累计的效果。比如:[10, 50, 50, 100]一路加到最后成为210

第二个processList()将之带入当下的sum值去到下一个递归。随着index的增加,该sum值会将一直被保留直到最终被加入result,它也将实现跳跃相加的方法。比如:[10, 50, 50, 100]其中的10 + 100就可以在这个递归中实现。

代码如下:

/**
 * Recursive function to collect all the sums from all combinations
 * @param {Array} coinList array of coins
 * @param {Number} n the length of coin array
 * @param {Number} sum the current accumulated value
 * @param {Number} currentIndex the current processing index in the coin array
 * @param {Object} results existing result object brought into recursive function for recording sum
 */
function processList(coinList, n, sum, currentIndex, results) {
    if(currentIndex > n) {
        return;
    }

    if(currentIndex === n){
        addToResults(results, sum);
        return;
    }

    processList(coinList, n, sum + coinList[currentIndex], currentIndex + 1, results);
    processList(coinList, n, sum, currentIndex + 1, results);
}
第四步 addToResults函数

因为result本身是空的,当我们算出一个和是result里没有的key的话,必须初始化这个key,使其值为1。反之,则只需要在其值上加1便可。

代码如下:

/**
 * Add the number to results as a key
 * @param {Object} results 
 * @param {Number} number 
 */
function addToResults(results, number) {
    if(results[number]) {
        results[number]++;
    } else {
        results[number] = 1;
    }
}
大功告成!

完整代码:

/**
 * 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]);
        }
    }

    processList(coinList, coinList.length, 0, 0, results);

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

/**
 * Recursive function to collect all the sums from all combinations
 * @param {Array} coinList array of coins
 * @param {Number} n the length of coin array
 * @param {Number} sum the current accumulated value
 * @param {Number} currentIndex the current processing index in the coin array
 * @param {Object} results existing result object brought into recursive function for recording sum
 */
function processList(coinList, n, sum, currentIndex, results) {
    if(currentIndex > n) {
        return;
    }

    if(currentIndex === n){
        addToResults(results, sum);
        return;
    }

    processList(coinList, n, sum + coinList[currentIndex], currentIndex + 1, results);
    processList(coinList, n, sum, currentIndex + 1, results);
}

/**
 * Add the number to results as a key
 * @param {Object} results 
 * @param {Number} number 
 */
function addToResults(results, number) {
    if(results[number]) {
        results[number]++;
    } else {
        results[number] = 1;
    }
}

测试:

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

console.log("Result:", countDistinctSum(a, b)) // Result: 9

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

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

相关文章

  • 多少硬币组合,更优解法

    摘要:写在前面的自我检讨上周我发布了一篇博文有多少种硬币组合找出独特子数组之和,是关于有多少种硬币组合的算法题的解法。假如,现在我们只有一个硬币,分。则可能性只有种,那就是。 写在前面的自我检讨 v2 上周我发布了一篇博文有多少种硬币组合——找出独特子数组之和,是关于有多少种硬币组合的算法题的解法。虽然算法本身能够给出一个正确答案,可是仔细想来,我却没办法给出一个简单直接的解释为什么这样跑可...

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

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

    henry14 评论0 收藏0
  • 对比特币的继承与创新!深入理解公链 CKB 的 Cell 模型

    摘要:为了理解底层公链的模型,我们前置了几篇概念性文章,讲述了我们应该以状态为中心设计区块链系统的,以及这么做带来的好处。交易依然表示状态的变化迁移。 为了理解底层公链 CKB 的 Cell 模型,我们前置了几篇概念性文章,讲述了我们应该以状态为中心设计区块链系统的,以及这么做带来的好处。并且在上一篇文章中,详细分析了比特币 UTXO 模型和以太坊的 Account 模型,以及进行了对比分析...

    ruicbAndroid 评论0 收藏0
  • 前端也需要好好的精进自己的算法

    摘要:算法前端发展的再快,也不要忘记精进自己的算法,算法是灵魂和核心。我会把我刷过的算法题总结归类,不断完善。 算法 前端发展的再快,也不要忘记精进自己的算法,算法是灵魂和核心。我会把我刷过的算法题总结归类,不断完善。欢迎大家关注。 数组和堆栈 数组去重 旋转数组 如何快速找出两个数之和等于某一个值的两个数? 快排 排序算法大总结 快速找到数组中的最大值 多维数组的展开 二分查找 有效的括...

    hersion 评论0 收藏0
  • JavaScript 编程精解 中文第三版 十六、项目:平台游戏

    摘要:来源编程精解中文第三版翻译项目原文译者飞龙协议自豪地采用谷歌翻译部分参考了编程精解第版所有现实都是游戏。黑色的方块表示玩家,玩家任务是收集黄色的方块硬币,同时避免碰到红色素材岩浆。网格中的元素可能是空气固体或岩浆。 来源:ApacheCN『JavaScript 编程精解 中文第三版』翻译项目原文:Project: A Platform Game 译者:飞龙 协议:CC BY-NC-SA...

    wayneli 评论0 收藏0

发表评论

0条评论

xiaoqibTn

|高级讲师

TA的文章

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