背包问题(1):关于零一背包问题的个人理解

一.经典背包问题(零一背包)

给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。问题的名称来源于如何选择最合适的物品放置于给定背包中。(每个物品只能选择一次)

解题思路:

1.建立二维dp数组,如dp[i][j](含义为:在遍历遇到物品i(物品的下标)时,背包的重量为j时,此时背包的总价值为dp[i][j];(如dp[1][1]的含义为在遇到物品1,背包重量为j时,总价值为dp[i][j])

2.确定递推公式

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

//其中dp[i - 1][j - weight[i]] + value[i] 可以这样理解:
//此时背包最大容量为j ,若要将物品j放入背包则需要为其腾出相应的空间weight[i];

3.根据实际情况初始化数组;

​
for(int i = 0 ; i <=  MAX_WEIGHT; i++){                //MAX_WEIGHT为背包所能装的最大的容量
        if(weight[0] <= i) dp[0][i] = weight[i];       //当背包容量大于的第一个物品的重量时
        else dp[0][i] = 0;                                //可以将物品放入背包
}

​for(int i = 0; i < weight.length; i++){                //weigth.length为物品的数量                     
        dp[i][0] = 0;                                  //当背包容量为0时,背包中价值一直为0
}

4.为了便于理解,外层先遍历物品,里层遍历背包的重量

​
for(int i = 1 ; i < weight.length ; i++){            //物品
    for(int j = 0 ; j < MAX_WEIGHT ; j++){            //背包
        if(j >= weight[i])
            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[i - 1][j - weight[i]] + value[i] 可以这样理解:
//此时背包最大容量为j ,若要将物品j放入背包则需要为其腾出相应的空间weight[i];

​

5.举例例子,打印dp数组确定对错性。

(ps:在遍历顺序中内外层的遍历可互换,不影响结果)

额外补充:

可以背包问题中的dp二维数组压缩成一维的dp数组(滚动数组),则变化成dp[j] ,其含义为背包最大重量为j时,所背最大的价值为dp[j]

则初始化为 dp[0] = 0;

遍历顺序为

//此处内外层不可互换,且必须为倒序遍历,否则变为完全背包为问题
for(int i = 1 ; i < weight.length ; i++){            //物品
    for(int j = MAX_WEIGHT ; j > weigth[i] ; j--){            //背包
        dp[j] = Math.max(dp[j] ,dp[j - weight[i]] + value[i]);
    }
}

(详细可看:代码随想录:带你学透01背包问题(滚动数组篇) | 从此对背包问题不再迷茫!_哔哩哔哩_bilibili)

二,力扣中零一背包的变式及其解题思路

1.分割等和子式

 主要解题思路:
将nums数组抽象为物品集,其中同一物品的重量和价格两者相等,算出nums数组数组总和得到sum,其中target = sum / 2 ,将问题转化成单纯的经典的零一背包问题:有一个物品集为nums ,求在最大容量target的情况下,背包中的物品的价值是否能达到target。

具体代码

class Solution {
    public boolean canPartition(int[] nums) {
        int sum = 0;
        int lengths = nums.length;
        for(int i = 0; i < lengths ;i++){
            sum += nums[i];
        }
        if(sum % 2 == 1) return false;
        int target = sum / 2;
        int [] dp = new int [target + 1]; 
        for(int i = 1 ; i < lengths; i++){
            for(int j = target; j >= nums[i] ; j--){
                    dp[j] = Math.max(dp[j],dp[j - nums[i]] + nums[i]);        
            }
        }
        if(dp[target] == target) return true;
        return false;
    }
}

2.最后一块石头的重量II

 主要解题思路:

与上一题相似,stone[i] 抽象成重量价值相统一的物品集,难点在于找出target值,根据题意,所要求的是碰撞后剩下的石头重量,可以将石头堆尽可能分为重量相等的两堆,即target = sum / 2; 

将问题转化成单纯的经典的零一背包问题:有一个物品集为stone ,求在最大容量target的情况下,求背包中的物品最大价值。(最后返回:sum - 2 * dp[target])。

具体实现代码:

class Solution {
    public int lastStoneWeightII(int[] stones) {
        int sum = 0 ;
        int lengths = stones.length;
        for(int i = 0; i < lengths ;i++){
            sum += stones[i];
        }
        int target = sum / 2;
        int [] dp = new int [target + 1];
        dp[0] = 0;
        for(int i = 0; i < lengths;i++){
            for(int j = target ; j >= stones[i]; j--){
                dp[j] = Math.max(dp[j] , dp[j - stones[i]] + stones[i]);
            }
        }
        return sum - 2 * dp[target];
    }
}

   3.目标和

 解题思路:

与前面的题目一样,将nums转化为物品集 ,痛点仍然是找出背包最大值target ,这里的思路与石头那道有点相似,可以将nums分为两堆进行相减,得到目标和,将正数堆设为left ,负数堆设为right ,则 left - right == 目标和 ,left + right == sum , 两条式子可以解出两个未知量 ,则

left = (sum + 目标和) / 2 ,得出背包最大值target(target = left);

但是不同于石头的是这里求的是达成目标和的方式的种数,现在dp数组的含义发生变化:

dp[j] :装满背包容量为j的背包,一共有dp[ j ]方法

则将递推公式修改为

dp[j] += dp[j - nums[i]];

初始化为

dp[0] = 1;

(为啥子dp[ 0 ] == 1 ? 可以想象为当物品数量为1,物品的重量刚好为0 ,则dp[0] = 1 ,实在想不出更好的原因和例子(苦笑))

则现在将其转化成有一个物品集为nums ,装满最大容量为j的背包 ,最多有多少种方法。

实现代码如下:

class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        int lengths = nums.length;
        int sum = 0;
        for(int i = 0 ; i < lengths;i++){
            sum += nums[i];
        }
        int left = (sum + target) / 2;
        if((sum + target) % 2 == 1 || left < 0) return 0;
        int [] dp = new int [left + 1];
        dp[0] = 1;
        for(int i = 0; i < lengths;i++){
            for(int j = left ; j >= nums[i];j--){
                dp[j] += dp[j - nums[i]];
            }
        }
        return dp[left];
    }
}

4.一和零(零一背包的变式:多重背包)

解题思路:

本题仍是考察我们的抽象思维,如果能将其抽象为背包问题就已经成功了一半;

将strs数组中的字符串中的0抽象成重量weight,1抽象为value,因为要同时考虑两个维度则将

dp数组变成二维dp[ i ][ j ]  ,含义:

背包最大重量为i ,背包最大价值为 j 时 ,背包最多能装 dp[ i ][ j ]个物品;

递推公式为 :(这里dp[ j ][ k ] 即为 dp[ i ][ j ])

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

 代码实现:

class Solution {
    public int findMaxForm(String[] strs, int m, int n) {
        int lengths = strs.length;
        int [] weight = new int [lengths];
        int [] value = new int [lengths];
        //算出每个物品(每个字符串)的 0 和 1 分别的数量
        for(int i = 0; i < lengths;i++){
            for(int j = 0; j < strs[i].length();j++){        
                if(strs[i].charAt(j) == '1') weight[i]++;
                else value[i]++;
            }
        }
        int [][] dp = new int [n + 1][m + 1];
        for(int i = 0 ; i < lengths;i++){
            for(int j = n ; j >= weight[i] ;j--){
                for(int k = m ; k >= value[i]; k--){
                    dp[j][k] = Math.max(dp[j - weight[i]][k - value[i]] + 1,dp[j][k]);
                }
            }
        }
        return dp[n][m];
    }
}

结尾:

本人第一次撰写博客文章,如有不足,还请海涵。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值