题目与题解
01背包问题,你该了解这些!
题目链接:01背包问题,你该了解这些!
代码随想录题解:01背包问题,你该了解这些!
视频讲解:带你学透0-1背包问题!| 关于背包问题,你不清楚的地方,这里都讲了!| 动态规划经典问题 | 数据结构与算法_哔哩哔哩_bilibili
解题思路:
直接看答案
看完代码随想录之后的想法
1. 确定dp数组以及下标的含义
对于背包问题,有一种写法, 是使用二维数组,即dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。
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无法放进背包中,所以背包内的价值依然和前面相同。)
- 放物品i:由dp[i - 1][j - weight[i]]推出,dp[i - 1][j - weight[i]] 为背包容量为j - weight[i]的时候不放物品i的最大价值,那么dp[i - 1][j - weight[i]] + value[i] (物品i的价值),就是背包放物品i得到的最大价值
所以递归公式: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
3. dp数组如何初始化
首先从dp[i][j]的定义出发,如果背包容量j为0的话,即dp[i][0],无论是选取哪些物品,背包价值总和一定为0
状态转移方程 dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); 可以看出i 是由 i-1 推导出来,那么i为0的时候就一定要初始化。
dp[0][j],即:i为0,存放编号0的物品的时候,各个容量的背包所能存放的最大价值。
那么很明显当 j < weight[0]的时候,dp[0][j] 应该是 0,因为背包容量比编号0的物品重量还小。
当j >= weight[0]时,dp[0][j] 应该是value[0],因为背包容量放足够放编号0物品。
4. 遍历顺序:按行(先遍历物品)或按列(先遍历背包重量)都可以
5. 举例
import java.util.*;
public class ID46Kama {
public static void main (String[] args) {
/* code */
Scanner scanner = new Scanner(System.in);
int m = scanner.nextInt();
int n = scanner.nextInt();
int[] weight = new int[m];
int[] value = new int[m];
for(int i = 0; i < m; i++) {
weight[i] = scanner.nextInt();
}
for(int i = 0; i < m; i++) {
value[i] = scanner.nextInt();
}
// 二维dp
int[][] dp = new int[m][n+1];
for (int j = 0; j <= n; j++) {
if (weight[0] <= j )
dp[0][j] = value[0];
}
for(int i = 1; i < m; i++) {
for(int j = 1; j <= n; j++) {
if (j - weight[i] >= 0) {
dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j - weight[i]]+value[i]);
} else {
dp[i][j] = dp[i-1][j];
}
}
}
System.out.println(dp[m-1][n]);
}
}
遇到的困难
Scanner用法又不太记得了,用ACM模式写很容易写错,各种小错误,真难
01背包问题,你该了解这些! 滚动数组
题目链接:01背包问题,你该了解这些! 滚动数组
代码随想录题解:01背包问题,你该了解这些! 滚动数组
解题思路:
看答案
看完代码随想录之后的想法
二维数组可以压缩为一维数组,如果把dp[i - 1]那一层拷贝到dp[i]上,表达式完全可以是:dp[i][j] = max(dp[i][j], dp[i][j - weight[i]] + value[i]);
1. 确定dp数组的定义
在一维dp数组中,dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j]。
2. 一维dp数组的递推公式
dp[j]有两个选择,一个是取自己dp[j] 相当于 二维dp数组中的dp[i-1][j],即不放物品i,一个是取dp[j - weight[i]] + value[i],即放物品i,指定是取最大的,毕竟是求最大价值,
所以递归公式为:
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
可以看出相对于二维dp数组的写法,就是把dp[i][j]中i的维度去掉了。
3. 一维dp数组如何初始化
dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j],那么dp[0]就应该是0,因为背包容量为0所背的物品的最大价值就是0。
dp数组在推导的时候一定是取价值最大的数,如果题目给的价值都是正整数那么非0下标都初始化为0就可以了。
假设物品价值都是大于0的,所以dp数组初始化的时候,都初始为0就可以了
4. 一维dp数组遍历顺序
二维dp遍历的时候,背包容量是从小到大,而一维dp遍历的时候,背包是从大到小。并且只能从物品开始遍历,不能从背包重量遍历。这里看不太懂,后续再说吧
5. 举例
public class ID46Kama {
public static void main (String[] args) {
/* code */
Scanner scanner = new Scanner(System.in);
int m = scanner.nextInt();
int n = scanner.nextInt();
int[] weight = new int[m];
int[] value = new int[m];
for(int i = 0; i < m; i++) {
weight[i] = scanner.nextInt();
}
for(int i = 0; i < m; i++) {
value[i] = scanner.nextInt();
}
// 一维dp
int[] dp1 = new int[n+1];
for (int i = 1; i < m; i++) {
for (int j = n; j >= weight[i]; j++) {
dp1[j] = Math.max(dp1[j], dp1[j - weight[i]]+value[i]);
}
}
System.out.println(dp1[n]);
}
}
遇到的困难
着实不太看得懂遍历顺序,感觉是为了避免用上一行计算当前行的时候,出现用当前行的结果计算当前行的情况。
416. 分割等和子集
题目链接:416. 分割等和子集
代码随想录题解:416. 分割等和子集
解题思路:
乍一看感觉就是组合问题,可以用回溯做。
首先对输入数组求和,然后判断和是不是偶数。如果是的话,题目就转换成求数组中是否存在一个组合,其和为sum/2;否则直接return false。
但是随想录说回溯会超时,而且这里要求用背包做,就没有写。
看完代码随想录之后的想法
只有确定了如下四点,才能把01背包问题套到本题上来。
- 背包的体积为sum / 2
- 背包要放入的商品(集合里的元素)重量为 元素的数值,价值也为元素的数值
- 背包如果正好装满,说明找到了总和为 sum / 2 的子集。
- 背包中每一个元素是不可重复放入
应用题转换为数学题:背包大小target为sum/2,每个数价值为nums[i],重量也为nums[i],求背包中是否存在dp[target]=target的情况。
class Solution {
public boolean canPartition(int[] nums) {
int sum = 0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
}
if (sum % 2 == 1) 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]);
}
}
return dp[target] == target;
}
}
也可以剪枝一下,在循环内提前判断dp[target] == target是否成立。
遇到的困难
这道题无论如何都想不到要用背包的,真的很难想。
今日收获
学习了一下01背包问题的求解方法,只能说啥也不会,全都靠背。