1、题目描述
2、解题思路
本题有两个状态:硬币数组、金额
设 dp[i][j] 表示前 i 个硬币中组成金额 j 的组合数。(前 i 个硬币即 coins[0]、…、coins[i-1])
在计算 dp[i][j] 时有两个选择:
1、一定不包含第 i-1 个硬币,此时 dp[i][j] = dp[i-1][j],即让前 i-1 的硬币去组成金额 j;
2、一定包含第 i-1 个硬币,此时 dp[i][j] = dp[i][j-coins[i-1]],即已经包含了第 i-1 个硬币,此时金额剩下 j-coins[i-1] ,假设每个面额的硬币可以选择无数个,因此变成从前 i 个硬币中组成金额 j-coins[i-1] 的组合数。
当 j-coins[i-1] < 0 ,说明第 i-1 个硬币面额都比金额 j 还大,只能不包含它,选择 1;
当 j-coins[i-1] ≥ 0,说明第 i-1 个硬币可以包含和不包含,选择 1+2;
从上面的分析中可以知道,dp 始终和 dp[i][…] 和 dp[i-1][…] 有关,因此我们可以压缩一下状态。
设 dp[j] 表示对于硬币组成金额 j 的组合数。
虽然状态变成了一个,但是还是得有两层 for 循环遍历,外层是前 i 个硬币,内层是金额。
此时的选择虽然也是两个:包不包含第 i-1 个硬币,但是我们的状态只和金额有关,因此,只要不涉及金额的变化,dp[j] 就是原值。
当 j-coins[i-1] ≥ 0 时,说明第 i-1 个硬币属于可选可不选,dp[j] = dp[j] + dp[j-coins[i-1]],第一个加数为不包含,第二个加数为包含。
3、解题代码
// 二维 dp
class Solution {
public int change(int amount, int[] coins) {
int len = coins.length; // int n = coins.length;
int[][] dp = new int[len + 1][amount + 1];
// base_case
for (int i = 0; i <= len; i++) {
dp[i][0] = 1;
}
for (int i = 1; i <= len; i++) {
for (int j = 1; j <= amount; j++) {
if (j - coins[i - 1] < 0) { // 第 i-1 个硬币面额都比 j 大,无法包含
dp[i][j] = dp[i - 1][j];
} else {
dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i - 1]];
}
}
}
return dp[len][amount];
}
}
// 一维 dp
class Solution {
public int change(int amount, int[] coins) {
int n = coins.length;
int[] dp = new int[amount + 1];
dp[0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= amount; j++) {
if (j - coins[i - 1] >= 0) {
dp[j] = dp[j] + dp[j - coins[i - 1]];
}
}
}
return dp[amount];
}
}