代码随想录 (programmercarl.com)
01背包:每个物品只能够使用一次
1.确定dp数组以及下标的含义
dp[i][j]: 下标为[0, i]之间的物品,任取放进容量为j的背包里的价值大小
2.确定递推公式
(1)如果背包重量 < 物品重量
无法放物品i,价值为dp[i][j] = dp[i - 1][j];
(2)其他情况:
①不放物品i,价值为dp[i][j] = dp[i - 1][j];(跳过了这个物品,所以剩下的物品是在[0, i - 1]之间了
②放物品i, 价值为dp[i - 1][j - weight[i]] + value[i]
所以递推公式为:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
3.初始化
根据①可知,要想获得该位置的价值,要用到上方的值;
根据②可知,要想获得该位置的价值,要用到左上方的值(因为i - 1在上方,j - weight[i] + value[i]在左边,背包重量减轻了)。
所以,初始化最左边一列和最上边一行即可。
其中,最左边一列初始化为0,最上边一行初始化为物品0的价值(注意重量是否超过背包重量)。
4.遍历顺序
从前往后遍历,两个for循环可以调换顺序。
先遍历物品再遍历背包这个顺序更好理解
import java.util.Arrays;
/**
* ClassName: Test
* Package: PACKAGE_NAME
*/
public class Test {
public static void main(String[] args) {
int[] weight = {1, 3, 4};
int[] value = {15, 20, 30};
int bagSize = 4;
testWeightBagProblem(weight, value, bagSize);
}
/**
* 动态规划获得结果
*
* @param weight 物品的重量
* @param value 物品的价值
* @param bagSize 背包的容量
*/
public static void testWeightBagProblem(int[] weight, int[] value, int bagSize) {
//创建数组
int[][] dp = new int[weight.length][bagSize + 1];
//初始化最上面一行
for (int j = weight[0]; j <= bagSize; j++) {
dp[0][j] = value[0];
}
//本来需要将最左边一列初始化为0,但其实整个二维数组在创建时就会初始化为0,所以可以省略
//遍历物品和背包
for (int i = 1; i < weight.length; i++) {
for (int j = 1; j <= bagSize; j++) {
if (j < weight[i]) {
dp[i][j] = dp[i - 1][j];
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
}
}
}
// 打印dp数组
for(int[] arr : dp){
System.out.println(Arrays.toString(arr));
}
/* // 打印dp数组
for (int i = 0; i < weight.length; i++) {
for (int j = 0; j <= bagSize; j++) {
System.out.print(dp[i][j] + "\t");
}
System.out.println("\n");
}*/
}
}
当前层是通过上一层进行计算的,那我们就可以把上一层拷贝到当前层直接进行计算。
就把一个矩阵压缩成一行来计算,将二维降低到一维。
1.确定dp数组以及下标的含义
dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j]。
2.确定递推公式
(1)如果背包重量 < 物品重量
无法放物品i,价值为dp[j] = dp[j];
(2)其他情况:
①不放物品i,价值为dp[j] = dp[j];(跳过了这个物品,所以剩下的物品是在[0, i - 1]之间了)
②放物品i, 价值为dp[j - weight[i]] + value[i]
所以递推公式为:dp[i][j] = max(dp[j], d[j - weight[i]] + value[i]);
3.初始化
dp[0] = 0; 非零下标全部初始化为0
4.遍历顺序
dp[1] = dp[1 - 1] + 15 = 15;
dp[2] = dp[2 - 1] + 15 = 30;
说明dp[2]的时候把物品0放进背包里2次,所以不能从前往后遍历,需要从后往前遍历背包。
先遍历物品,再遍历背包。
从后往前遍历,两个for循环不可以调换顺序,只能先遍历物品再遍历背包。
数据循环滚动运用,可以尝试自己修改遍历顺序,打印dp数组尝试
【面试问题】
为什么二维背包的遍历顺序可以是先背包再物品,或者先物品再背包?
虽然两个for循环遍历的次序不同,但是dp[i][j]所需要的数据就是左上角,根本不影响dp[i][j]公式的推导。
为什么二维dp数组遍历的时候不用倒序呢?
因为对于二维dp,dp[i][j]都是通过上一层即dp[i - 1][j]计算而来,本层的dp[i][j]并不会被覆盖。
为什么一维背包的两个for循环不能调换顺序?为什么需要从后往前遍历?
右下角的值依赖上一层左上角的值,因此需要保证左边的值仍然是上一层的,从右向左覆盖。(二维的时候当前位置的值是由上面和左上角的值决定的,现在压缩为一维,覆盖了上面的值,就只由左边的值决定了)
倒序遍历是为了保证物品i只被放入一次。
public class Test {
public static void main(String[] args) {
int[] weight = {1, 3, 4};
int[] value = {15, 20, 30};
int bagWeight = 4;
testWeightBagProblem(weight, value, bagWeight);
}
public static void testWeightBagProblem(int[] weight, int[] value, int bagWeight) {
//创建dp数组并进行初始化
int[] dp = new int[bagWeight + 1];
//递推公式
for (int i = 0; i < weight.length; i++) {
for (int j = bagWeight; j >= weight[i] ; j--) {//当j < weight[i]的时候,直接跳过,背包放不下了
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
}
}
//遍历输出
for (int j = 0; j <= bagWeight; j++) {
System.out.print(dp[j] + " ");
}
}
}
将该问题抽象为一个0-1背包问题,给定的非空数组数值nums[i]同时当作物品的weight和value。
其中背包的容量为j,是将数组数值相加求和 / 2得到的。
也就是说,这个分割等和子集问题变为了判断这些物品能否把背包装满的问题。
1.确定dp数组以及下标的含义
dp[j]:背包容量为j,其价值为dp[j]
2.确定递推公式
01背包的递推公式为:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
本题物品i的重量是nums[i],价值也是nums[i]。
所以递推公式:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
3.初始化
dp[0] = 0; 非零下标全部初始化为0,方便后面更改数据取最大值。
4.遍历顺序
先物品再背包,一维背包压缩了数组,不能颠倒两个for循环。
背包后序遍历,确保每个物品只能使用一次。
可以尝试打印一下正序遍历的dp数组,物品就不止使用一次了。
正序遍历背包的话,会出现:
数组[1, 5, 11, 5],dp[1] = 1, dp[2] = 2,说明此时1使用了两次,不符合题目要求的一个物品只能使用一次。
class Solution {
public boolean canPartition(int[] nums) {
if(nums == null || nums.length == 0) {
return false;
}
int sum = 0;
for (int num : nums) {
sum += num;
}
//总和为奇数,不能平分
if(sum % 2 != 0) {
return false;
}
int target = sum / 2;
int[] dp = new int[target + 1];
for (int i = 0; i < nums.length; i++) {
for (int j = target; j >= nums[i]; j--){
dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
}
if (dp[target] == target){
return true;
}
}
return dp[target] == target;
}
}