有N件物品和一个最多能背重量为W的背包。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。
01背包和完全背包唯一不同就是体现在遍历顺序上
完全背包因为允许一件物品被取多次,所以是正序遍历而非倒序遍历
完全背包先遍历物品还是先遍历背包无所谓,但是如果是先遍历背包再遍历物品那就需要做一个判断 if (i - weight[j] >= 0)
1.dp含义
dp[j]:背包容量为j时背包的最大价值
2.dp初始化
3.递推公式
和01背包一模一样
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
4.遍历方式
正序遍历,先遍历背包或者物品都无所谓
5.模拟推导
代码实现
//先遍历物品,再遍历背包
private static void testCompletePack(){
int[] weight = {1, 3, 4};
int[] value = {15, 20, 30};
int bagWeight = 4;
int[] dp = new int[bagWeight + 1];
for (int i = 0; i < weight.length; i++){ // 遍历物品
for (int j = weight[i]; j <= bagWeight; j++){ // 遍历背包容量
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
}
}
for (int maxValue : dp){
System.out.println(maxValue + " ");
}
}
//先遍历背包,再遍历物品
private static void testCompletePackAnotherWay(){
int[] weight = {1, 3, 4};
int[] value = {15, 20, 30};
int bagWeight = 4;
int[] dp = new int[bagWeight + 1];
for (int i = 1; i <= bagWeight; i++){ // 遍历背包容量
for (int j = 0; j < weight.length; j++){ // 遍历物品
if (i - weight[j] >= 0){
dp[i] = Math.max(dp[i], dp[i - weight[j]] + value[j]);
}
}
}
for (int maxValue : dp){
System.out.println(maxValue + " ");
}
}
最后,又可以出一道面试题了,就是纯完全背包,要求先用二维dp数组实现,然后再用一维dp数组实现,最后再问,两个for循环的先后是否可以颠倒?为什么? 这个简单的完全背包问题,估计就可以难住不少候选人了
思路
完全背包问题:我们求的组成amount的组合数,amount相当于背包容量,物品价值和物品重量都是coins[i]
1.dp含义
dp[i]表示凑成总金额为i的货币组合数
2.初始化dp
dp[0]=1,因为后台测试数据默认amount=0时组合数是为1的
3.递推公式
求的是组合数,dp[j]就等于所有的dp[j - coins[i]](考虑coins[i]的情况)相加。
4.遍历顺序
先遍历物品还是现遍历背包呢,都可以,这里我是先遍历物品,但是要判断coins[i]与我们当前遍历的背包的容量j的大小关系,这有点类似于剪枝操作
代码实现
class Solution {
public int change(int amount, int[] coins) {
//完全背包问题
//dp[i]表示凑成总金额i的货币组合数
int[] dp=new int[amount+1];
//初始化dp
dp[0]=1;//因为后台测试数据是默认,amount = 0 的情况,组合数为1的。
for(int i=0;i
看到这个结论,但我感觉不太对(因为我觉得不是绝对的吧):
如果求组合数就是外层for循环遍历物品,内层for遍历背包。
如果求排列数就是外层for遍历背包,内层for循环遍历物品。
这道题是排列,不是组合了,完全背包问题,这道题让我联想到了爬楼梯那道,因为后面的dp[i]会依赖dp[i-1]
动规五部曲分析如下:
1.确定dp数组以及下标的含义
dp[i]: 凑成目标正整数为i的排列个数为dp[i]
2.确定递推公式
求装满背包有几种方法,递推公式一般都是dp[i] += dp[i - nums[j]];
3.dp数组如何初始化
因为递推公式dp[i] += dp[i - nums[j]]的缘故,dp[0]要初始化为1,这样递归其他dp[i]的时候才会有数值基础。
4.确定遍历顺序
如果把遍历nums(物品)放在外循环,遍历target的作为内循环的话,举一个例子:计算dp[4]的时候,结果集只有 {1,3} 这样的集合,不会有{3,1}这样的集合,因为nums遍历放在外层,3只能出现在1后面!
本题遍历顺序最终遍历顺序:target(背包)放在外循环,将nums(物品)放在内循环,内循环从前到后遍历
5.举例来推导dp数组
代码实现
class Solution {
public int combinationSum4(int[] nums, int target) {
//完全背包
//dp[j]含义是组成目标整数为j的方法个数
//这道题求的是排列
int[] dp=new int[target+1];
dp[0]=1;//初始化
for(int j=1;j<=target;j++){
for(int i=0;i