【动态规划之背包问题】——完全背包问题(518. 零钱兑换 II)
完全背包问题
题目描述
给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。
示例 1:
输入: amount = 5, coins = [1, 2, 5]
输出: 4
解释: 有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
示例 2:
输入: amount = 3, coins = [2]
输出: 0
解释: 只用面额2的硬币不能凑成总金额3。
示例 3:
输入: amount = 10, coins = [10]
输出: 1
解题思路
转换为背包问题的描述形式:
有一个背包, 最打容量为 amount , 有一系列物品 coins , 每个物品的重量为 coins[i] , 每个物品的数量有限。 请问有多少种方法, 能够把背包恰好装满?
动态规划的套路:
- 要明确两点, 「状态」 和「选择」 。
状态有两个, 就是「背包的容量」 和「可选择的物品」 。
选择就是「装进背包」 或者「不装进背包」 。
框架:
for 状态1 in 状态1的所有取值:
for 状态2 in 状态2的所有取值:
for ...
dp[状态1][状态2][...] = 计算(选择1, 选择2...)
- 要明确 dp 数组的定义
-
dp[i][j]
的定义如下:若只使用前 i 个物品, 当背包容量为 j 时, 有dp[i][j]
种方法可以装满背包- 即 若只使用 coins 中的前 i 个硬币的面值, 若想凑出大额 j , 有
dp[i][j]
种凑法
- 即 若只使用 coins 中的前 i 个硬币的面值, 若想凑出大额 j , 有
-
答案就是
dp[N][amount
], N 为 coins 数组的大小 -
base case 为 dp[0][..] = 0, dp[..][0] = 1
。 因为如果不使用任何硬币面值, 就无法凑出任何金额; 如果凑出的目标金额为 0, 那么“无为而治”就是唯一的一种凑法。
-
细化后的框架:
int dp[N+1][amount+1]
dp[0][..] = 0
dp[..][0] = 1
for i in [1..N]:
for j in [1..amount]:
把物品 i 装进背包,
不把物品 i 装进背包
return dp[N][amount]
- 根据「选择」 , 思考状态转移的逻辑
dp[i][w]
表示: 对于前 i 个物品, 当前背包的容量为 w 时, 这种情况
下可以装下的最大价值是dp[i][w]
。- 如果你没有把这第 i 个物品装进背包, 那么很显然, 不使用
coins[i]
这个面值的硬币, 那么凑出面额 j 的方法数dp[i][j]
应该等于dp[i-1][j]
,继承之前的结果。 coins[i]
这个面值的硬币, 那么dp[i][j]
应该等于dp[i][j-coins[i-1]]
。其中:dp[i][j-coins[i-1]]
也不难理解, 如果你决定使用这个面值的硬币, 那么就应该关注如何凑出金额j - coins[i-1]
。
- 如果你没有把这第 i 个物品装进背包, 那么很显然, 不使用
dp[i][j]
是「共有多少种凑法」 , 所以dp[i][j]
的值应该是以上两种选择的结果之和
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= amount; j++) {
if (j - coins[i-1] >= 0)
dp[i][j] = dp[i - 1][j]
+ dp[i][j-coins[i-1]];
return dp[N][W]
完整代码求解为:
二维数组:
public static int change_1(int amount,int[] coins){
int n = coins.length;
int[][] dp = new int[n + 1][amount + 1];
//base case
for (int i = 0; i <= n; i++) {
dp[i][0] = i;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= amount; j++) {
if (j - coins[i - 1] >= 0){
dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i - 1]];
}else {
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[n][amount];
}
一维数组:
//状态压缩,二维变一维
public static int change(int amount,int[] coins) {
int n = coins.length;
int[] dp = new int[amount + 1];
dp[0] = 1;//base case
for (int i = 0; i < n; i++) {
for (int j = 1; j <= amount ; j++) {
if (j - coins[i] >= 0){
dp[j] = dp[j] + dp[j - coins[i]];
}
}
}
return dp[amount];
}
参考:labuladong