本篇文章介绍「完全背包」问题,但「0-1 背包」「子集背包」和今天介绍的「完全背包」是一个系列的问题!!
关于「0-1 背包」的详细介绍可见 经典动态规划:0-1 背包问题
关于「子集背包」的详细介绍可见 经典动态规划:子集背包问题
「完全背包」和其它两种背包唯一的区别就是:物品数量无限
由于只有「物品数量」上的不同,所以其它地方几乎一样,只是dp
的定义不同,以及状态转移不一样而已!!
状态:物品数量 & 背包容量
选择:是否装入物品
dp
数组的定义dp[i][j]
定义如下:对于前i
个物品,当前背包容量为j
时,恰好装满的组合数量
我们也可以很快确定 base case。即:dp[...][0] = 1
,因为背包容量为 0,无论物品数量是多少,均只有一种组合,什么也不装!
dp[i][j]
= 不把物品i
装进背包的组合数 + 把物品i
装进背包的组合数
如果不把物品i
装进背包,显然组合数应该等于dp[i-1][j]
,继承前i-1
个物品的结果
如果把物品i
装进背包,那么组合数应该等于dp[i][j - val[i-1]]
注意:这里是dp[i][j - val[i-1]]
,而不是dp[i-1][j - val[i-1]]
,因为物品数量是无限的,所以选择物品i
装入背包后,还可以继续选择物品i
装入背包
对于题目 零钱兑换 II,状态转移方程为:
public int change(int amount, int[] coins) {
int[][] dp = new int[coins.length + 1][amount + 1];
// base case
for (int i = 0; i <= coins.length; i++) dp[i][0] = 1;
for (int i = 1; i <= coins.length; i++) {
for (int j = 1; j <= amount; j++) {
// 装不下的情况
if (j - coins[i - 1] < 0) dp[i][j] = dp[i - 1][j];
else dp[i][j] = dp[i][j - coins[i - 1]] + dp[i - 1][j];
}
}
return dp[coins.length][amount];
}
关于空间优化的技巧可见 动态规划之最长回文子序列「dp 空间优化」
public int change(int amount, int[] coins) {
int[] dp = new int[amount + 1];
// base case
dp[0] = 1;
for (int i = 1; i <= coins.length; i++) {
for (int j = 1; j <= amount; j++) {
// 装不下
// if (j - coins[i - 1] < 0) dp[j] = dp[j];
// else dp[j] = dp[j - coins[i - 1]] + dp[j];
// 下面是合并写法
if (j - coins[i - 1] >= 0) dp[j] = dp[j - coins[i - 1]] + dp[j];
}
}
return dp[amount];
}