硬币找零问题是动态规划的一个经典问题,其中最少硬币找零是一个变种,本篇将参照上一篇01背包问题的解题思路,来详细讲解一下最少硬币找零问题。若是你须要查看上一篇,能够点击下面连接: 详解动态规划01背包问题--JavaScript实现 数组
也能够查看下一篇 详解动态规划最长公共子序列--JavaScript实现浏览器
下面让咱们开始吧。bash
给定4种面额的硬币1分,2分,5分,6分,若是要找11分的零钱,怎么作才能使得找的硬币数量总和最少。post
最少硬币找零问题,是为了求硬币的组合,因此一个大前提是硬币无限量供应。咱们创建以下表格来分析问题: ui
其中每列用j表示零钱总额,每行i表示硬币面额。T[i][j]
表示硬币个数,它是咱们即将填入表格的数字。spa
在填写表格以前,咱们须要先明确几个规则:3d
当咱们只能使用面额为1分的硬币时,根据上面的规则,那么很显然,总额为几分,就须要几个硬币。即T[i][j] = j
。code
当咱们有1分和2分两种面额时,那么组合方式就相对多了点。cdn
i=1 j = 1
:总额为1时,只能使用1分的面额。即填1。 i=1 j = 2
:总额为2时,可使用2个1分的,也可使用1个2分的。由于咱们要求最少硬币,因此使用1个2分的。表格所表达的意思是硬币的数量,因此这里也填1。 i=1 j = 3
:总额为3时,可使用3个1分的,也可使用1个1分加1个2分。所以这里应该填2。 i=1 j = 4
:总额为4时,可使用4个1分的,可使用2个1分加1个2分,也可使用2个2分。其中硬币最少的状况应该是2个2分。所以这里填2。 i=1 j = 5
:总额为5时,组合就更多了,可是聪明的你应该能想到使用2个2分加1个1分,能够实现最少硬币的需求。所以这里填3。blog
咱们来看填写完上面5格后的状况:
建议你本身再纸上照着我这图画一个表格。接下来,别急着填表。咱们要根据已有的数据,总结出T[i][j]
的规律,而后经过填写剩余表格来验证。
咱们将硬币面额使用数组coins[i]来表示,根据表格有 1分=coins[0], 2分=coins[1]。 当j<coins[i]时,T[i][j]
的值,应该等于它的同列,上一行,即便T[i][j] == T[i-1][j]
。 好比咱们从表中所看到的,T[1][1]==T[0][1]
。 当j>=coins[i]时,根据已有的 i=1行能够推出一个规律,令a = 1+T[i][j-coins[i]]
,T[i][j]= min(T[i-1][j],a)
,即两者比较取最小值。可能一开始你看到这个关于a的公式,有点太忽然,难以接受。稍微解释一下,当第i行,优先选择这同样的硬币,由于这一行的硬币面额最大,最有可能使得总硬币数量最少。所以j-coins[i]
,就很好理解了,就是选择了这一行的硬币后,还剩下多少总额。举个例子,当i=1,j=3时,j-coins[1]=1。那么选择2分后,还剩余总额为1,这时候咱们再定位到i=1,j=1,即T[1][1],它的值为1,再加上一个常数1,即得最终结果2。
再举例,i=1 j=5
。因为是从左到右填表的,因此i=1,j<5的表格都填完了。j-coins[i]=3,定位到T[1][3]=2
,加上常数1,即得最后结果T[1][5]=3
。
其实公式自己很短,也很好记。若是实在没法理解,建议先不用纠结。先最小化浏览器,不要看本篇剩余的内容。带着这个解题公式,本身在纸上,把这个表格填写完整,在填表分析的过程当中就能慢慢理解了。
按照上一步所提供的公式,其实全部的T[i][j]
均可以填完了。以下表格。
建议先本身再纸上填表,填完了,再和个人图对比一下,看是否答案存在出入。
以上的填表逻辑,使用伪代码表示以下
if(i == 0){
T[i][j] = j/coins[i]; //硬币找零必定要有个 最小面额1,不然会无解
}else{
if(j >= coins[i]){
T[i][j] = min(T[i-1][j],1+T[i][j-coins[i]])
}else{
T[i][j] = T[i-1][j];
}
}
复制代码
至此,填完表格咱们已经接近完成了。接下来要寻找从表格中寻找硬币组合。🤔
与填表顺序相反,寻找组合从有下角开始。
首先须要明确的是若是T[i][j] == T[i-1][j]
,那么就向上搜索。根据图来分析:
1. 定位到T[3][11]
,因为不存在T[i][j] == T[i-1][j]
,因此不用向上搜索,肯定选中一个6分
硬币。寻找组合的思路和填写T[i][j]的思路几乎是反过来的。
2. 选择一个6分硬币后,剩余的总额为11-6=5。所以定位到T[3][5]中。因为T[3][5]==T[2][5],所以看图中的蓝色箭头,向上搜索,直到T[i][j] != T[i-1]
。
3. 定位到T[2][5]中,此时coins[i]为5分。选中5分硬币只有,剩余的总额为5-5=0。
4. 当j=0时,搜索结束。由上面步骤肯定选中的硬币组合为:1个5分,1个6分。
以上就是整个最少硬币找零问题的分析思路。最终代码使用 JavaScript 实现,若是你的 Sublime 支持纯 JavaScript,你能够直接复制黏贴代码,command + b 直接运行查看结果,而后修改输入变量,查看更多状况下的输出结果。
//动态规划 -- 硬币找零问题
function minCoins(coins,total,n){
var T = [];
for(let i = 0;i<n;i++){
T[i] = []
for (let j=0;j<= total;j++){
if(j == 0){
T[i][j] = 0;
continue;
}
if(i == 0){
T[i][j] = j/coins[i]; //硬币找零必定要有个 最小面额1,不然会无解
}else{
if(j >= coins[i]){
T[i][j] = Math.min(T[i-1][j],1+T[i][j-coins[i]])
}else{
T[i][j] = T[i-1][j];
}
}
}
}
findValue(coins,total,n,T);
return T;
}
function findValue(coins,total,n,T){
var i = n-1, j = total;
while(i>0 && j >0){
if(T[i][j]!=T[i-1][j]){
//锁定位置,肯定i,j值,开始找构成结果的硬币组合。 其实根据这种计算方法,只须要考虑最右边那一列,从下往上推。
//console.log(T[i][j]);
break
}else{
i--;
}
}
var s = []; //存储组合结果
while(i >= 0 && j > 0 ){
s.push(coins[i]);
j=j-coins[i];
if(j <= 0){
break; //计算结束,退出循环
}
//若是 i == 0,那么就在第 0 行一直循环计算,直到 j=0便可
if(i>0){
//console.log(i);
while(T[i][j] == T[i-1][j]){
i--;
if(i== 0){
break;
}
}
}
}
console.log(s);
//能够把数组s return 回去
}
var coins = [1,2,5,6];
var total = 11
var n = coins.length
console.log(minCoins(coins,total,n));
复制代码