链接:代码随想录背包问题
背包问题
背包问题是动态规划中基本的问题,我们考虑下面的简单问题:
假设背包容量为4,有物品0,1,2,要使背包放的物品价值最大,并且总重量不超过背包容量,应该怎么放?
二维数组下的做法如下:
1、创建二维数组dp,其中dp[i][j]的含义是在0~i物品中任意取,放到容量为j的背包里,能够获得的最大价值。
2、确定递推公式
对于dp[i][j],假设不取第i个物品,那么dp[i][j]=dp[i-1][j];假设取第i的物品,那么dp[i][j]=dp[i-1][j-weight[i]]+value[i]。为了获得更大的物品价值,dp[i][j]要取两者的最大值,即递推公式为:
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])
3、初始化dp数组
背包容量为0时,对于j=0时,dp[i][0]=0;对于i=0时,只有当j>=weight[i]时,dp[0][j]=0。在这个例子中初始化dp如下:
4、确定遍历顺序
可以先遍历背包,再遍历物品,也可以先遍历物品,再遍历背包。
5、举例推导dp数组
注意:做动态规划时,最好把dp能写出来,遇到有报错可以打印dp数组进行对比,看哪里出错。
空间复杂度优化
二维数组可以优化成一维数组,即将一个一维数组重复利用,称之为滚动数组。只保留一个和背包容量+1的一维数组,遍历物品,对每个物品,更新这个一维数组,由上面的递推公式,dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]),可以改写成dp[i][j] = max(dp[i][j], dp[i][j - weight[i]] + value[i]),相当于更新dp[i][j]前,它表示上一层的dp[i-1][j],更新后,它表示dp[i][j]。而如果我们从小容量背包到大容量背包遍历,会出现前面小容量的背包不再是上一层的dp,而是已经更新过的dp,因此这样遍历会出现重复计算物品的现象。因此对于背包应该从大背包向小背包遍历。五部曲分析:
1、确定dp和含义,dp是长度和背包长度+1的一维数组,dp[j]表示容量为j的背包能装的最大物品价值
2、确定递推公式
dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
3、dp数组初始化
由于dp[j]代表容量为1的背包装的最大物品价值,因此j=0时dp[0]=0,其余情况也可以赋dp[j]=0,因为后面会将dp[j]覆盖。
4、遍历顺序
只能从后往前遍历,即从大背包向小背包遍历,防止之前的数据被覆盖。
5、举例推导dp数组
Java代码
public static void main(String[] args) {
int[] weight = {1, 3, 4};
int[] value = {15, 20, 30};
int bagWight = 4;
testWeightBagProblem(weight, value, bagWight);
}
public static void testWeightBagProblem(int[] weight, int[] value, int bagWeight){
int wLen = weight.length;
//定义dp数组:dp[j]表示背包容量为j时,能获得的最大价值
int[] dp = new int[bagWeight + 1];
//遍历顺序:先遍历物品,再遍历背包容量
for (int i = 0; i < wLen; i++){
for (int j = bagWeight; j >= weight[i]; j--){
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
}
}
//打印dp数组
for (int j = 0; j <= bagWeight; j++){
System.out.print(dp[j] + " ");
}
}