持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第17天,点击查看活动详情
每日刷题 2022.06.15
- leetcode原题链接:leetcode.cn/problems/co…
- 难度:中等
- 方法:完全背包
题目
- 给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
- 计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
- 你可以认为每种硬币的数量是无限的。
示例
- 示例1
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
- 示例2
输入: coins = [2], amount = 3
输出: -1
- 示例3
输入: coins = [1], amount = 0
输出: 0
提示
1 <= coins.length <= 121 <= coins[i] <= 2^31 - 10 <= amount <= 1^04
解题思路
- 与本题类似的题目:279.完全平方数
- 另外一道题2310. 个位数字为 K 的整数之和,和本题也很相似,都是完全背包,都是一样的思路。可以一起练习。
- 学习
01背包问题后的实际应用题目
分析
f[i][j]表示:前i个面值的硬币,凑成总和为j,所需的最小硬币数。- 具体分析:对于第
i个数字,设第i个数字的面值为money,那么就有:- 选
0个数字i,此时有f[i][j] = f[i - 1][j - 0 * money] = f[i - 1][j] - 选
1个数字i,此时有f[i][j] = f[i - 1][j - 1 * money] - 选
2个数字i,此时有f[i][j] = f[i - 1][j - 2 * money] - 选
k个数字i,此时有f[i][j] = f[i - 1][j - k * money]
- 选
- 因为每种不同面值的硬币🪙都是无限的,因此可以需要依次去尝试取多个的最小值。
- 对于
f[0][j]也就是硬币面值为0的时候,那么是没有最小值的,初始化为:Infinity,因为除了num = 0的时候,其他的时候,都是无法到达的。 - 由此可得状态转移方程:
f[i][j] = min(f[i - 1][j], f[i - 1][j - k * money] + k) && k * money < j
需要注意的点
f数组的长度,因为我们第0行用来存储不能够达到的情况,因此与coins数组的下标就会相差1,所以在声明f数组的时候,长度要声明成n + 1
AC代码
/**
* @param {number[]} coins
* @param {number} amount
* @return {number}
*/
var coinChange = function(coins, amount) {
let n = coins.length, f = new Array(n + 1).fill(0).map(() => new Array(amount + 1).fill(0));
// 当前可以使用的硬币数为`0`,那么不论总金额为多少,都无法达成,因此初始化为Infinity
// 需要注意:f[0][0] = 0是可以达成的,初始化为0
for(let i = 1; i <= amount; i++) {
f[0][i] = Infinity;
}
// f[i][j]表示:当前第i个面值的硬币,凑成总和为j,所需最少硬币个数
for(let i = 1; i <= n; i++) {
for(let j = 0; j <= amount; j++) {
// 不选当前面值的硬币
f[i][j] = f[i - 1][j];
// 选择当前面值的硬币
for(let k = 0; k * coins[i - 1] <= j; k++) {
if(f[i - 1][j - k * coins[i - 1]] != Infinity){
// 如果选择当前面值的硬币,是无法达成的,就不需要取最小了,因为只能不选取该面值的硬币
f[i][j] = Math.min(f[i][j], f[i - 1][j - k * coins[i - 1]] + k);
}
}
}
}
// 最终返回结果即可
return f[n][amount] == Infinity ? -1 : f[n][amount];
};