开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第9天,点击查看活动详情
518. 零钱兑换 II 题目描述:给你一个整数数组 表示不同面额的硬币,另给一个整数 表示总金额。请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 。假设每一种面额的硬币有无限个。
| 示例 |
|---|
| 输入: 输出: 解释: 有四种方式可以凑成总金额: |
中规中矩的动态规划
典型的 完全背包 组合问题:每种物品(硬币)无限选择,仅考虑整体,不考虑排序,背包容量为 。
1、确定 dp 数组及含义
💥 为选择 区间时,凑成总和为 的组合数,其中 ,,。
2、确定 dp 状态方程
如果不选择 ,那么
其中,包括两种情况,
-
主动放弃 ,与背包容量无关;
-
被动放弃 ,容量所限,即 , 为正整数。
如果选择 ,则有,,其中,。
3、确定 dp 初始状态
当背包容量恒为 (),从 区间选择银币凑出总和为 的组合数只有 种(不选择银币),即
- 要从 区间选择银币(只能选择 )凑出总和为 。如果容量为 的背包恰能装下 个 ,其中 是正整数,那么组合数即为 ,否则为 。即,
4、确定遍历顺序
对于完全背包 组合 问题,应先遍历物品(),再遍历背包(),可参考 # 重识背包问题(下)
-
外循环遍历从 到
-
内循环遍历从 到
5、确定返回值
回归到 定义中,返回值即为
6、代码示例
/**
* 空间复杂度 O(amount * n),n是数组coins的长度
* 时间复杂度 O(amount * n)
*/
function change(amount: number, coins: number[]): number {
const n = coins.length;
const dp = Array.from({ length: n }, () => new Array(amount + 1).fill(0));
for (let i = 0; i < n; i++) {
dp[i][0] = 1;
}
// 背包容量不能被coins[0]整除,说明这种组合不成立,组合数为0,否则为1
for (let j = 1; j <= amount; j++) {
dp[0][j] = (j % coins[0]) ? 0 : 1;
}
for (let i = 1; i < n; i++) {
for (let j = 1; j <= amount; j++) {
dp[i][j] = dp[i - 1][j];
for (let k = 1; k * coins[i] <= j; k++) {
dp[i][j] += dp[i - 1][j - k * coins[i]]
}
}
}
return dp[n - 1][amount];
};
思维提升-哨兵
"方法1: 中规中矩的动态规划"中初始化条件较为复杂,我们在 的 方向的作用域拓展一个“哨兵”,辅助计算。
1、确定 dp 状态数组
表示选择 区间时,凑成总和为 的组合数,其中 ,,。
💥NOTE,和法1相比,
-
表示 (法1是 )区间凑成总和为 的组合数;
-
的取值范围变为 (法1是 )。
2、确定 dp 状态方程
与法1相同,
-
如果不选择 ,则有
-
如果选择 ,则有
其中,。
3、确定 dp 初始状态
当背包容量恒为 (),从 区间选择银币凑出总和为 的组合数只有 种(这个区间无硬币可选,那总和必然为 ),即 。
4、确定遍历顺序
-
第一层遍历从 到 (第一层遍历的终点不同,法1是到 )
-
第二层遍历从 到
5、确定最终返回值
6、代码示例
/**
* 空间复杂度 O(amount * n),其中n是coins数组长度
* 时间复杂度 O(amount * n)
*/
function change(amount: number, coins: number[]): number {
const n = coins.length;
const dp = Array.from({ length: n + 1 }, () => new Array(amount + 1).fill(0));
dp[0][0] = 1;
for (let i = 1; i <= n; i++) {
const coin = coins[i - 1];
for (let j = 0; j <= amount; j++) {
dp[i][j] = dp[i - 1][j];
for (let k = 1; k * coin <= j; k++) {
dp[i][j] += dp[i - 1][j - k * coin];
}
}
}
return dp[n][amount];
};
状态压缩:此时定义 为凑成总和为 j 的组合数,其实 ,且初始条件为凑成总和为 的组合数为 ,那就只有不选择任何银币,即 。
/**
* 空间复杂度 O(amount)
* 时间复杂度 O(amount * coins.length)
*/
function change(amount: number, coins: number[]): number {
const dp = new Array(amount + 1).fill(0);
dp[0] = 1;
for (const coin of coins) {
for (let i = coin; i <= amount; i++) {
dp[i] += dp[i - coin];
}
}
return dp[amount];
};