状态压缩技巧总结(01背包)


前言

能够使用状态压缩技巧的动态规划都是二维dp问题,看它的状态转移方程,如果计算状态dp[i][j]需要的都是dp[i][j]相邻的状态,那么就可以使用状态压缩技巧,将二维的dp数组转化成一维,将空间复杂度从 O(N^2) 降低到 O(N)。


0-1背包状态压缩

定义:dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。

 //遍历顺序:先遍历物品,再遍历背包容量
int wlen = weight.length;
//定义dp数组:dp[i][j]表示背包容量为j时,前i个物品能获得的最大价值
int[][] dp = new int[wlen + 1][bagsize + 1];
//遍历顺序:先遍历物品,再遍历背包容量
for (int i = 1; i <= wlen; i++){
    
    
    for (int j = 1; j <= bagsize; j++){
    
    
        if (j < weight[i - 1]){
    
    
            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]);
        }
    }
}
return dp[wlen][bagsize];

第一步: 二维转一维

在使用二维数组的时候,递推公式核心部分可以简化为:

dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i-1]] + value[i-1]);

可以看出,只需要第i-1层的dp值就能推导第i层的dp值,而整个int[wlen + 1][bagsize + 1]的数组空间存在冗余。如果把dp[i - 1]那一层拷贝到dp[i]上,表达式完全可以是:

dp[i][j] = max(dp[i][j], dp[i][j - weight[i-1]] + value[i-1]);

与其把dp[i - 1]这一层拷贝到dp[i]上,不如只用一个一维数组了,只用dp[j](一维数组,也可以理解是一个滚动数组)。

dp[j] = max(dp[j], dp[j - weight[i-1]] + value[i-1]);

这就是滚动数组的由来,需要满足的条件是上一层可以重复利用,直接拷贝到当前层。

第二步:遍历顺序

请添加图片描述
如图所示,要压缩空间首先就要将i-1行的数据复制到第i行,若按照原来的遍历顺序使j从1加到bagsize,来求dp[j]的值,则会存在被覆盖的问题。
例如:求dp[2]会用到第i-1行dp[i-1][2]以及在这之前的数据,求得

dp[i][2]=max(dp[i-1][2], dp[i-1][2 - weight[i-1]] + value[i-1]);

然而,将新求得的dp[2](即dp[i][2])填入压缩后的一维数组时会覆盖原来的dp[i-1][2]】的值,而导致后面计算dp[3]、dp[4]等dp值时,若再要用到dp[2],拿到的不再是想要dp[i-1][2]值,而是新求得的dp[2]值。
所以在第二个循环要从后往前遍历,完整代码如下

int wLen = weight.length;
//定义dp数组:dp[j]表示背包容量为j时,能获得的最大价值
 int[] dp = new int[bagWeight + 1];
 //遍历顺序:先遍历物品,再遍历背包容量
 for (int i = 0; i < wLen; i++){
    
    
     for (int j = bagWeight; j >= weight[i]; j--){
    
    //逆序遍历
         dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
     }
 }
return dp[bagWeight];

猜你喜欢

转载自blog.csdn.net/qq_41777702/article/details/125285526