背包问题的分类

今天题目的难点在于如何理解将二维dp数组转换成一维的dp数组,我们先看下题目:
题目描述
小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。他需要带一些研究材料,但是他的行李箱空间有限。这些研究材料包括实验设备、文献资料和实验样本等等,它们各自占据不同的空间,并且具有不同的价值。
小明的行李空间为 N,问小明应该如何抉择,才能携带最大价值的研究材料,每种研究材料只能选择一次,并且只有选与不选两种选择,不能进行切割。
输入描述
第一行包含两个正整数,第一个整数 M 代表研究材料的种类,第二个正整数 N,代表小明的行李空间。
第二行包含 M 个正整数,代表每种研究材料的所占空间。
第三行包含 M 个正整数,代表每种研究材料的价值。
输出描述
输出一个整数,代表小明能够携带的研究材料的最大价值。
输入示例
6 1
2 2 3 1 5 2
2 3 1 5 4 3
输出示例
5
提示信息
小明能够携带 6 种研究材料,但是行李空间只有 1,而占用空间为 1 的研究材料价值为 5,所以最终答案输出 5。
数据范围:
1 <= N <= 1000
1 <= M <= 1000
研究材料占用空间和价值都小于等于 1000
按照卡哥的动规五部曲思路我们进行如下分析(感谢卡哥)
- 第一步:确定dp[i][j]的具体含义:表示从下标为[0 - i-1]的物品里任意取,放进容量为j的背包,价值总和最大是多少。
- 第二步:初始化 dp 数组做了简化(给物品增加冗余维)。这样初始化dp数组,默认全为0即可。这一步其实就是模仿背包重量从 0 开始,背包容量 j 为 0 的话,即dp[i][0],无论是选取哪些物品,背包价值总和一定为 0。可选物品也可以从无开始,也就是没有物品可选,即dp[0][j],这样无论背包容量为多少,背包价值总和一定为 0。
- 第三步:确定递推公式
- 当前背包的容量都没有当前物品i大的时候,是不放物品i的,那么前i-1个物品能放下的最大价值就是当前情况的最大价值
- 当前背包的容量可以放下物品i,那么此时分两种情况:
- 1、不放物品i
- 2、放物品i
- 比较这两种情况下,哪种背包中物品的最大价值最大
- 第四步:确定遍历顺序
- 第五步:打印dp数组
java
/**
* @param weight 物品的重量
* @param value 物品的价值
* @param bagSize 背包的容量
*/
public static void testWeightBagProblem(int[] weight, int[] value, int bagSize) {
// 创建dp数组
int goods = weight.length; // 获取物品的数量
int[][] dp = new int[goods + 1][bagSize + 1]; // 给物品增加冗余维,i = 0 表示没有物品可选
// 初始化dp数组,默认全为0即可
// 填充dp数组
for (int i = 1; i <= goods; i++) {
for (int j = 1; j <= bagSize; j++) {
if (j < weight[i - 1]) { // i - 1 对应物品 i
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]); // i - 1 对应物品 i
}
}
}
// 打印dp数组
for (int[] arr : dp) {
System.out.println(Arrays.toString(arr));
}
}
那么如何将二维数组优化成一维的? 我们看下代码:
java
private static void testWeightBagProblem_3(int[] weight, int[] value, int bagSize) {
int[] dp = new int[bagSize + 1];
dp[0] = 0;
for (int i = 1; i <= bagSize; i++) {
dp[i] = value[0];
}
for (int j = 1; j < value.length; j++) {
for (int i = bagSize; i >= weight[j]; i--) {
dp[i] = Math.max(dp[i], dp[i - weight[j]] + value[j]);
}
}
}
注意到了吗?跟二维的有什么区别?
倒叙遍历! 为什么要倒叙遍历?
由于一位数组是根据二维优化而来的,因此如果仍然从前向后遍历,遍历到后面的时候前面的元素可能已经被修改过了,此时dp[i - weight[j]]再获取到的就不是上一行的值了,因此需要倒叙遍历,以确保dp[i - weight[j]]获取到的仍是上一行的值。