Day50 01背包问题(一维dp数组)LeetCode416. 分割等和子集

01背包问题之一维dp数组:

        01背包问题二维dp数组:dp[i][j]表示任选物品0 ~ i放进容量为j的背包里所能获得的最大价值,其中dp递推公式为:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]),其中如果不放物品i,那么最大价值为任选物品0 ~ i - 1放进容量为j的背包里所能获得的最大价值;如果放物品i,那么最大价值为任选物品0 ~ i - 1放进容量为j - weight[i]的背包里加上物品i的价值所能获得的最大价值。遍历顺序:先遍历物品,再遍历容量,正序遍历。

        将二维dp数组压缩成一维dp数组:dp[j]表示容量为j的背包,所背的物品价值可以最大为dp[j]。dp递推公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]),此时dp[j]有两个选择,一个是取自己dp[j] 相当于 二维dp数组中的dp[i-1][j],即不放物品i,一个是取dp[j - weight[i]] + value[i],即放物品i,指定是取最大的,毕竟是求最大价值。遍历顺序:先遍历背包容量,再遍历物品,其中遍历背包容量是倒序遍历。

至于为什么背包容量是倒序遍历:for(int j = bagweight; j >= weight[i]; j--)?

 首先要明白二维数组的递推过程,然后才能看懂二维变一维的过程。
 假设目前有背包容量为10,可以装的最大价值, 记为g(10)。

 即将进来的物品重量为6。价值为9。
那么此时可以选择装该物品或者不装该物品。
如果不装该物品,显然背包容量无变化,这里对应二维数组,其实就是取该格子上方的格子复制下来,就是所说的滚动下来,直接g【10】 = g【10】,这两个g【10】要搞清楚,右边的g【10】是上一轮记录的,也就是对应二维数组里上一层的值,而左边是新的g【10】,也就是对应二维数组里下一层的值。

如果装该物品,则背包容量= g(10-6) = g(4) + 9 ,也就是 g(10) = g(4) + 6 ,这里的6显然就是新进来的物品的价值,g(10)就是新记录的,对应二维数组里下一层的值,而这里的g(4)是对应二维数组里上一层的值,通俗的来讲:你要找到上一层也就是上一状态下 背包容量为4时的能装的最大价值,用它来更新下一层的这一状态,也就是加入了价值为9的物品的新状态。

这时候如果是正序遍历会怎么样? g(10) = g(4) + 6 ,这个式子里的g(4)就不再是上一层的了,因为你是正序啊,g(4) 比g(10)提前更新,那么此时程序已经没法读取到上一层的g(4)了,新更新的下一层的g(4)覆盖掉了,这里也就是为啥有题解说一件物品被拿了两次的原因。

题目:416. 分割等和子集 - 力扣(LeetCode)

思路:

        属于01背包问题,判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。等价于任选数组中的元素放进背包里,如果背包里的元素和数组总和的一半,说明可以分割成两个子集。其中元素的值就是物品的容量,也就是物品的价值,那么使得背包的元素和就是背包的最大价值。

dp[j]表示背包元素容量为j是,获得的最大价值dp[j]。那么如果背包容量为target, dp[target]就是装满 背包之后的重量,所以 当 dp[target] == target 的时候,背包就装满了。

dp递推公式:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i])

 代码:

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        // dp[j]中的j表示背包内总和,数组元素值num[i]等价于物品价值value[i],等价于物品重量weight[i]
        // 题目中说:每个数组中的元素不会超过 100,数组的大小不会超过 200
        // 总和不会大于20000,背包最大只需要其中一半,所以10001大小就可以了
        vector<int> dp(10001, 0);
        dp[0] = 0;
        int sum = 0;
        for(int num : nums){
            sum += num;
        }
        //如果和为奇数,不可能分割成两个子集,直接返回false
        if(sum % 2 == 1) return false;

        for(int i = 0; i < nums.size(); i++){
            for(int j = sum / 2; j >= nums[i]; j--){
                dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
            }
        }
        //如果任取数组中的元素放进容量为 sum / 2 的背包里,其最大价值也就是数组和等于背包容量,说明可以分割成两个子集
        return dp[sum / 2] == sum / 2;
    }
};

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值