1. 01背包:二维朴素写法
public static int getMaxValue(int[] weight, int[] values, int w) {
int n = weight.length;
int[][] dp = new int[n + 1][w + 1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= w; j++) {
if (j >= weight[i - 1]) {
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i - 1]] + values[i - 1]);
}
}
}
return dp[n][w];
}
2. 01背包:二维朴素写法 —> 一维空间优化写法
public static int getMaxValue2(int[] weight, int[] values, int w) {
int n = weight.length;
int[] dp = new int[w + 1];
for (int i = 1; i <= n; i++) {
for (int j = w; j >= weight[i-1]; j--) {
if (j >= weight[i - 1]) {
dp[j] = Math.max(dp[j], dp[j - weight[i - 1]] + values[i - 1]);
}
}
}
return dp[w];
}
3. 以上二维转一维 两个重点问题
3.1 为何可以用一维数组代替二维数组?
二维数组更新方式为
f[i][j] = f[i - 1][j] # 不含i的所有选法的最大价值
if j >= v[i]: # 判断含i的选法是否成立
f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i])
可以看出f[i][:]只依赖于f[i-1][:],所以根本没必要保留之前的f[i-2][:]等状态值;
使得空间从o(n*m)缩小到o(m),n,m分别为物品个数和背包体积
其实每个物品的体积和价值在该层循环结束后也不会再用上,这里也可以压缩为两个o(1)的空间
3.2 、为何要逆序更新?
一维数组更新方式为
while j >= v[i]:
dp[j] = max(dp[j], dp[j - v[i]] + w[i])
我们只有上一层dp值的一维数组,更新dp值只能原地滚动更改;
注意到,
当我们更新索引值较大的dp值时,需要用到索引值较小的上一层dp值dp[j - v[i]];
也就是说,在更新索引值较大的dp值之前,索引值较小的上一层dp值必须还在,还没被更新;
所以只能索引从大到小更新。
4. 两种写法的动态规划求解过程
4.1 二维动态规划 过程
例如,假设有5个物品,它们的重量和价值分别为:
物品编号 | 重量 | 价值 |
---|---|---|
1 | 2 | 3 |
2 | 3 | 4 |
3 | 4 | 5 |
4 | 5 | 6 |
5 | 9 | 10 |
填写动态规划表:按照递推关系,从左到右,从上到下依次计算f [i] [j]的值,并记录在表格中。例如,对于上面给出的例子,动态规划表如下:
容量 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
f[0] 前0个元素最大价值 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
f[1] 前1个元素最大价值 | 0 | 0 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
f[2] 前2个元素最大价值 | 0 | 0 | 3 | 4 | 4 | 7 | 7 | 7 | 7 |
f[3] 前3个元素最大价值 | 0 | 0 | 3 | 4 | 4 | 7 | 8 | 9 | 9 |
f[4] 前4个元素最大价值 | 0 | 0 | 3 | 4 | 5 | 7 | 8 | 9 | 10 |
f[5] 前5个元素最大价值 | 0 | 0 | 3 | 4 | 5 | 7 | 8 | 9 | 10 |
4.2 一维动态规划过程
看上面这段话是不是理解起来比较难,那我们 模拟一遍 逆序的过程 一切疑问皆明了