先对上一篇0-1背包问题做一点补充(参考文章mp.weixin.qq.com/s/OzdkF30p5…)
/*
定义一个二阶矩阵dp[N+1][V+1],
这里之所以要N+1和V+1,是因为第0行表示只能选择第0个物品的时候,即没有物品的时候
第0列表示背包的体积为0的时候,即不能装任何东西的时候
dp[i][j]表示在 只能选择前i个物品,背包容量为j的情况下,背包中物品的最大价值
对于dp[i][j]有两种情况:
1. 不选择当前的第i件物品/第i件物品比背包容量要大,则dp[i][j] = dp[i-1][j]
2. 选择当前的第i件物品(潜在要求第i件物品体积小于等于背包总容量),则能装入的物品最大价值为:
当前物品的价值 加上 背包剩余容量在只能选前i-1件物品的情况下的最大价值
dp[i][j] = dp[i-1][j-v[i]] + w[i]
dp[i][j]在两种情况中选择比较大的情况作为当前的最优解;
*/
int[][] dp = new int[N+1][V+1];
dp[0][0] = 0;
for(int i = 1; i <= N; i++){
for(int j = 0; j <= V; j++){
if(j >= v[i]){
dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-v[i]] + w[i]);
}else{
dp[i][j] = dp[i-1][j];
}
}
}
可以根据「选择」对dp[i][j]得到以下状态转移:
不把这第i个物品装入背包,那么是否能够恰好装满背包,取决于上一个状态dp[i-1][j],继承之前的结果。
把这第i个物品装入了背包,那么是否能够恰好装满背包,取决于状态dp[i-1][j-v[i]]。
dp[i-1][j-v[i]]也很好理解:你如果装了第i个物品,就要看背包的剩余重量j-v[i]限制下是否能够被恰好装满。
换句话说,如果j-v[i]的重量可以被恰好装满,那么只要把第i个物品装进去,也可恰好装满j的重量;否则的话,重量j肯定是装不满的。
优化代码
注意到dp[i][j]都是通过上一行dp[i-1][..]转移过来的,之前的数据都不会再使用了。
所以,我们可以进行状态压缩,将二维dp数组压缩为一维,节约空间复杂度:
int[] dp = new int[V+1];
dp[0] = 0;
for(int i = 1; i <= N; i++){
for(int j = V; j >= v[i]; j--){
dp[j] = Math.max(dp[j], dp[j-v[i]] + w[i]);
}
}
这就是状态压缩,其实这段代码和之前的解法思路完全相同,只在一行dp数组上操作,i每进行一轮迭代,dp[j]其实就相当于dp[i-1][j],所以只需要一维数组就够用了。
唯一需要注意的是j应该从后往前反向遍历,因为每个物品(或者说数字)只能用一次,以免之前的结果影响其他的结果。
先去忙了。