总结:
1.在做题的时候,考虑是否是01背包的题型:01背包定义是选或者不选。一些题目看似表面跟01背包没有关系,但是经过题目剖析,列出一系列等式关系,进行转化,就会变成01背包。 【力扣494. 目标和】、并且01背包的背包容量一定小于题目给出的nums.length,因为01背包是选或者不选,所以背包的容量一定是很紧缺的。
2.利用滚动数组进行压缩,当只有一个背包容量时:就使用一维数组(那么for循环就是先遍历物品,再遍历背包内容 ),有两个背包的容量时,就使用二维数组(那么for循环就是只遍历2个背包的容量,遍历背包的容量一定是倒序遍历)
3.进行滚动数组:0/1背包:外循环物品(nums)、内循环背包容量(target)、遍历背包容量时要倒序并且背包容量要大于物品重量(target>=nums[i]);
4..递归公式模板
①最值问题:
dp[j] = max/min( dp[j], dp[ j - nums[i] ] + 1)
dp[j] = max/min( dp[j], dp[ j - nums[i] ] + nums[i] );
②存在问题(boolean):
dp[j] = dp[j] || dp[ j- nums[i] ];
③组合问题:
dp[j] += dp[ j-nums[i] ];
5.利用滚动数组,一定要记得初始化dp[0]!!! ——通常dp[0]=1 /dp[0]=true
求背包问题的最小值一般初始化dp[0]=0 且Arrays.fill(dp,Integer.MAX_VALUE)
内容知识来源
一篇文章吃透背包问题!(细致引入+解题模板+例题分析+代码呈现) - 分割等和子集 - 力扣(LeetCode) (leetcode-cn.com)
01 背包定义——选或者不选
有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大
法一:使用二维数组
1.定义dp数组:dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少
//定义dp数组:dp[i][j]表示背包容量为j时,前i个物品能获得的最大价值
int wlen = weight.length;//物品个数
int bagsize=4;//背包容量
int[][] dp = new int[wlen + 1][bagsize + 1];
2.确定递推公式:
再回顾一下dp[i][j]的含义:从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。
那么可以有两个方向推出来dp[i][j]
- 不放物品i:由dp[i - 1][j]推出,即背包容量为j,里面不放物品i的最大价值,此时dp[i][j]=dp[i - 1][j]。(其实就是当物品i的重量大于背包j的重量时,物品i无法放进背包中,所以背包内的价值依然和前面相同。)注意这里的区间是0-i之间的物品,不放i,但是i-1放了,所以是dp[i-1][j]
- 放物品i:当i放进去时,那么这时候整个物品集就被分成两部分,1到i-1和第i个,而这是i是确定要放进去的,那么就把j空间里的weight[i]给占据了,只剩下j-weight[i]的空间给前面i-1,那么只要这时候前面i-1在j-weight[i]空间里构造出最大价值,即dp[i - 1][j - weight[i]],再加上此时放入的i的价值value[i],就是dp[i][j]了-----放了物品i:那么他的价值为:value[i]+剩余物品放在容量为j-weight[i]的背包里
所以递归公式: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
3.dp数组初始化——即初始化第一行和第一列
第一列初始化为0即:容量为0的时候,任何物品都装不了,故最大价值为0。【在定义二维数组的时候,就已经初始化了】
第一行初始化:因为物品0的重量为1,所以在背包容量<1的,最大价值都是0,背包容量>物品0的重量,初始化为物品0的价值。
完整代码如下:
public static void main(String[] args) {
int[] weight = {1, 3, 4};
int[] value = {15, 20, 30};
int bagsize = 4;
testweightbagproblem(weight, value, bagsize);
}
public static void testweightbagproblem(int[] weight, int[] value, int bagsize){
int wlen = weight.length, value0 = 0;
//定义dp数组:dp[i][j]表示背包容量为j时,前i个物品能获得的最大价值
int[][] dp = new int[wlen + 1][bagsize + 1];
//初始化行:背包容量>物品0的重量,初始化为物品0的价值。
for (int j =weight[0] ; j<= wlen; i++){
dp[0][j] = value[0];
}
//遍历顺序:先遍历物品,再遍历背包容量 i表示物品,j表示背包容量
for (int i = 1; i <= wlen; i++){
for (int j = 1; j <= bagsize; j++){
//如果当前的背包容量<物品的重量——>那就是不取物品
if (j < weight[i - 1]){
dp[i][j] = dp[i - 1][j];
}else{
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i - 1]] + value[i - 1]);
}
}
}
//打印dp数组
for (int i = 0; i <= wlen; i++){
for (int j = 0; j <= bagsize; j++){
System.out.print(dp[i][j] + " ");
}
System.out.print("\n");
}
}
法二:一维数组(滚动数组)——利用滚动数组进行遍历背包容量时:一定是倒序遍历
定义dp数组:因为我们将二维数组进行压缩至一维数组,所以我们只要定义一维数组的背包的容量就行。即定义一维的数组的长度就是背包的容量+1
//定义dp数组:dp[j]表示背包容量为j时,能获得的最大价值
int[] dp = new int[bagWeight + 1];
递推公式:
- 不放物品i:因为重量没变化,所以没放,就表示不覆盖原来的数据即dp[j]=dp[j]
- 放物品i:dp[j]=dp[j - weight[i]] + value[i]
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
针对遍历顺序:一定是先遍历物品,再遍历背包容量。并且遍历物品是正序遍历,遍历背包容量一定是倒序遍历
一个背包的容量压缩至一维数组:那么for循环就是先遍历物品数量,再遍历背包容量
for (int i = 0; i < wLen; i++){//i是取不到wlen的
for (int j = bagWeight; j >= weight[i]; j--)
两个背包的容量就是压缩至二维数组:那么for循环就是只遍历2个背包的容量,遍历背包的容量一定是倒序遍历
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] + " ");
}
}