一些废话
上面几篇记录了关于动态规划的基础题,这一篇就记录一下动态规划的经典背包问题之——0-1背包的基础知识,这里拆分为两篇来写,一篇为二维dp数组,第二篇为滚动数组(一维数组)。
0-1背包引入
所谓背包,是动态规划的一类类型题,并且有一套通用的解题思路和解题模板,0-1背包中的0-1代表 放 与 不放 两种情况,0-1背包需要多次学习至熟练于心,能够在脑海中明确每一步的含义,话不多说。
物品的价值与重量如下表:
背包最大只能装重量 4,现在将物品放入背包中,求如何放、放哪些物品才可以使得背包所背物品的价值之和最大。使用动态规划五部曲分析解解试试。
动态规划五部曲
本篇使用 二维数组 解决该0-1背包。
确定dp数组及下标含义
使用二维数组,则为 dp[ i ] [ j ] ,其中 i,j 的含义与遍历顺序有关,这里先假设 i 代表第 i + 1 个物品(因为物品数组下标是从 0 开始)、j 代表背包可以装下的 重量 ,那么 dp[ i ] [ j ] 代表 从下标 0 ~ i 中选取物品放入限重为 j 的背包中得到的背包中物品的价值之和的最大值。
确定递推公式
确定递推公式必须要先捋清楚递推公式中的状态有哪些情况、从什么状态变化而来。推导0-1背包递推公式中,从遍历dp数组的过程来看,每次遍历dp数组(或者说每次确定每一个 dp[ i ] [ j ] )的过程中都有两个可以考虑的 状态 :放入当前物品到背包中、不放入当前物品到背包中。
- 放入当前物品到背包中
放入当前物品 i 到背包中,意味着要从 i - 1 中找到一个 dp[ i - 1 ] [ j - xxx ] 与当前物品 i 的价值 value[ i ] 相加得到当前的 dp[ i ] [ j ]。xxx 代表着当前物品 i 的重量 weight[ i ]。
- 不放入当前物品到背包中
不放入当前物品到背包中的情况则比较简单,直接沿用 dp[i - 1][ j ] 即可。
确定完了两种情况后,最终要取哪个?因为是要求得最大重量,故取最大值即可,当然,取最大值前还需要判断 j - weight[ i ]是否非负数,保证下标有意义,若不满足则直接沿用 dp[i - 1][ j ]即可。因此递推公式如下:
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
初始化dp数组
初始化需考虑递推公式,从递推公式可以知道,dp[ i ][ j ] 与 i - 1 和 j - weight[ i ] 相关(牢记 i 、j 、dp[ i ][ j ] 的含义),即求得 dp[ i ][ j ] 的前提是 dp[ i - 1 ][ 0 ] ~ dp[ i - 1 ][ j ] 已知,因此 i = 0 的情况必须要先初始化,初始化为多少呢?
初始化 i = 0 是什么意思?i 代表物品,dp[ 0 ][ j ] 代表将物品 0 放入背包中,那么当背包可以装下物品 0 时,即将 dp[ 0 ][ j ] 初始化为物品 0 的价值 value[ 0 ]。并且 dp[ 0 ][ j ] 也只能初始化为 0 或者 value[ 0 ],因为当前 i = 0,只能从物品 0 ~ i 中选取物品放入背包。
另外,因为背包限重为 0 时,代表着无法放入任何物品,因此不论 i 为多少,dp[ i ][ 0 ] 都为 0,由于Java中int数组默认值为 0,故可以不初始化。
for (int i = 0; i <= bag; i++) {
if (weight[0] <= i) dp[0][i] = value[0];
}
遍历顺序
最容易理解的是 外遍历物品内遍历背包,每遍历一次外层循环代表 从物品 0 ~ 物品 i 中选取物品放入背包。
for (int i = 1; i < value.length; i++) {//遍历物品
for (int j = 1; j <= bag; j++) {//遍历背包
//TODO 处理递推
}
}
也可以 外遍历背包内遍历物品,这怎么理解? dp[ i ][ j ] 从 dp[ i - 1 ][ 0 ] ~ dp[ i - 1 ][ j ] 中的某一个得来,所以,dp[ i ][ j ] 从左上面得来,如下图:
以此而来,如果先遍历物品再遍历背包,那么就是在二维数组上一行一行从左往右遍历;如果先遍历背包再遍历物品,那么就是在二维数组是一列一列从上到下遍历,这两种遍历方向都是符合递推公式的。因此外遍历背包内遍历物品也是可行的。
for (int i = 1; i <= bag; i++) {//遍历物品
for (int j = 1; j < value.length; j++) {//遍历背包
//TODO 处理递推
}
}
举例推导dp数组
完整代码实现
public class Bag01Demo {
public static void main(String[] args) {
int[] value = {15, 20, 30};//物品价值
int[] weight = {1, 3, 4};//物品重量
int bag = 4;//背包限重
int[][] dp = new int[value.length][bag + 1];
//初始化
for (int i = 0; i <= bag; i++) {
if (weight[0] <= i) dp[0][i] = value[0];
}
//遍历
for(int i = 1; i < value.length; i++) {//遍历物品
for(int j = 1; j <= bag; j++) {//遍历背包
if(weight[i] <= j) {
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];
}
}
//dp数组打印
for(int i = 0; i < dp.length; i++) {
for(int j = 0; j < dp[0].length; j++) {
System.out.print(dp[i][j] + " ");
}
System.out.println("");
}
}
}
《代码随想录》刷题记