给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
示例 1:
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
示例 2:输入: coins = [2], amount = 3
输出: -1
说明:
你可以认为每种硬币的数量是无限的。来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/coin-change
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
看到题目的第一个想法是贪心,先取尽可能多的最大面额的硬币,再往后取,面额递减把总金额取满,取不满就返回-1 。但显然这是不对的,肯定会有不用取满最大面额的情况,于是便想到可以递归地去找,从最大数目的最大面额开始,递减地找,于是就有了如下代码:
var coinChange = function (coins, amount) {
const dfs = function(coins, sum, flag, count) {
if (flag >= coins.length || sum < 0) { //(剪枝)
return;
}
for (let i = Math.floor(sum/coins[flag]); i >= 0; --i) {
let tempSum = sum - i * coins[flag];
let tempCount = count + i;
if (tempSum == 0) {
ans = ans > tempCount ? tempCount : ans;
break; //剪枝,也可以直接return
}
if (tempCount+1 >= ans) {
break; //剪枝,也可以直接return
}
dfs(coins, tempSum, flag+1, tempCount);
}
}
coins.sort((x, y) => x > y ? -1 : 1);
const MAX = 2147483647;
let ans = MAX;
dfs(coins, amount, 0, 0);
return ans == MAX ? -1 : ans;
}
我在注释中标了三处剪枝,其中第一处也可以算是正常的临界条件,但我看到leetcode上面有些人没加我就当成是一种剪枝了。
1. 临界条件这里的剪枝比较显而易见,当面额已经超过amount(sum)的时候,就没必要继续找了,注意此处不能取等,除非你单独加了另一条剪枝,当amount为0的时候直接输出0
2. 第二处剪枝其实跟第一处的意思差不多,如果等于0了就没必要继续往下找了
3. 第三处则在于,如果往后再找一步都不能更优的话,也没必要再找了,因为更小面额找到的肯定不会更少
上面这种解法跑出来的效果还是不错的(当然也有运气成分,不过基本是99%以上的):
另一方面也不难想到动态规划的解法:
于是我们可以自底向上递推出总币值为amount时所用硬币的最小值:
var coinChange = function (coins, amount) {
const MAX = 2147483647;
let ans = Array(amount+1).fill(MAX);
ans[0] = 0;
for (let i = 0; i < coins.length; ++i) {
let k = 1;
while (coins[i] * k <= amount) {
if (k < ans[coins[i]*k]) ans[coins[i]*k] = k;
k++;
}
}
for (let i = 1; i < amount; ++i) {
if (ans[i] != MAX) {
let temp = ans[i];
for (let j = 0; j < coins.length; ++j) {
if (i + coins[j] <= amount) {
ans[i+coins[j]] = temp+1 > ans[i+coins[j]] ? ans[i+coins[j]] : temp+1;
}
}
}
}
return ans[amount] == MAX ? -1 : ans[amount];
}