代码随想录算法训练营第42天 | 动态规划 part04 ● 背包问题二维● 背包问题&滚动数组 一维 ● 416. 分割等和子集

# 二维dp数组,01背包 

1.确定dp数组以及下标的含义

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

代码随想录算法训练营第42天 | 动态规划 part04 ● 背包问题二维● 背包问题&滚动数组 一维 ● 416. 分割等和子集_第1张图片

2. 代码随想录算法训练营第42天 | 动态规划 part04 ● 背包问题二维● 背包问题&滚动数组 一维 ● 416. 分割等和子集_第2张图片

gpt 解决我的困惑 

代码随想录算法训练营第42天 | 动态规划 part04 ● 背包问题二维● 背包问题&滚动数组 一维 ● 416. 分割等和子集_第3张图片 

3. 代码随想录算法训练营第42天 | 动态规划 part04 ● 背包问题二维● 背包问题&滚动数组 一维 ● 416. 分割等和子集_第4张图片

 另外:

当 j < weight[0]的时候,dp[0][j] 应该是 0,因为背包容量比编号0的物品重量还小。

当j >= weight[0]时,dp[0][j] 应该是value[0],因为背包容量放足够放编号0物品。

所以初始化总共有两部分(第一列,第一行)

其他格子值无所谓,反正每个格子是通过左上得出来

4. 遍历顺序 也很重要。会是有的题目的难点。

本题两层for loop 物品套背包容量或者背包容量套物品都可以,因为都是往右下走就行了

随想录写的是一个从0开始一个从1开始,但是我觉得 i j 都从1开始就行

5. 举例推导dp数组

在格子里自己推导一遍看看对不对

测试代码(用随想录的按自己偏好修改了一点)

void test_2d_bag_problem1(vector &weight, vector &value, int &bagweight) {
    

    // 二维数组+初始化1
    vector> dp(weight.size(), vector(bagweight + 1, 0));

    // 初始化2
    for (int j = weight[0]; j <= bagweight; j++) {
        dp[0][j] = value[0];
    }

    
    for(int i = 1; i < weight.size(); i++) { // 遍历物品
        for(int j = 1; j <= bagweight; j++) { // 遍历背包容量
            if (j < weight[i]) dp[i][j] = dp[i - 1][j];
            else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

        }
    }

    cout << dp[weight.size() - 1][bagweight] << endl;
}

int main() {
    vector weight = {1, 3, 4};
    vector value = {15, 20, 30};
    int bagweight = 4;

    test_2d_bag_problem1(weight,value,bagweight);
}

# 一维滚动dp数组,01背包 

代码随想录算法训练营第42天 | 动态规划 part04 ● 背包问题二维● 背包问题&滚动数组 一维 ● 416. 分割等和子集_第5张图片

 依然是两层for loop, ij都要有,但是只有一层数组了

! 一维数组遍历顺序只能物品(外层)套背包容量,因为我们必须一行行算出结果后下一行继续用

! 一维数组遍历顺序 背包容量 只能 倒序,因为要保证每个物品只用一次,具体理解:

二维次更新要看斜上方一个值(还是原来的没被覆盖),一维每次更新对应的只能看同行前面某个值,但是如果从前往后已经被这一行的一种新的情况覆盖了。但是如果从后往前的话, 用的dp[j - weight[i]]还是之前那行的

代码随想录算法训练营第42天 | 动态规划 part04 ● 背包问题二维● 背包问题&滚动数组 一维 ● 416. 分割等和子集_第6张图片

void test_1d_bag_problem1(vector &weight, vector &value, int &bagweight) {

    // 二维数组+初始化1
    vector dp(bagweight + 1, 0);

    // 初始化2
    for (int j = weight[0]; j <= bagweight; j++) {
        dp[j] = value[0];
    }

    
    for(int i = 0; i < weight.size(); i++) { // 遍历物品
        for(int j = bagweight; j >= weight[i]; j--) { // 遍历背包容量
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
        }
    }
    //for(int j = 1; j <= bagweight; j++) { //if (j >= weight[i])
    //原来是这样的,但是现在因为倒过来,不需要了,只用决定终止的点是weight[i]就行

    cout << dp[bagweight] << endl;
}

int main() {
    vector weight = {1, 3, 4};
    vector value = {15, 20, 30};
    int bagweight = 4;

    test_1d_bag_problem1(weight,value,bagweight);
}

面试的问法:

代码随想录算法训练营第42天 | 动态规划 part04 ● 背包问题二维● 背包问题&滚动数组 一维 ● 416. 分割等和子集_第7张图片

#416. 分割等和子集 

自己没想出来(快想出来了,j的值已经确定了是从0,1,逐渐到sum)

随想录思路:

1. 需要一个subset和是sum/2 (我居然没想出来这个),我一直在想两个subset之和相减为0

背包的体积为sum / 2,j 就是 从 0,1 逐渐到sum 

2. 背包中每一个元素是不可重复放入。(01背包)

3. 关键:"每一个元素的数值既是重量,也是价值” 作为重量的话,是用来限制不能超过sum/2 ,做为价值是我们希望他的和尽可能大,以至于到达sum/2。

4. 关键:背包如果正好装满,说明找到了总和为 sum / 2 的子集。

不能装满的情况也是有的:比如 1 5 11 5 ,这个list ,当 j =7 时(因为我们要一直连续过去,所以才会出现这个值)最多1+5只能6,所以就是不能装满

 求和也可以用库函数:int sum = accumulate(nums.begin(), nums.end(), 0);

bool canPartition(vector& nums) {
        int sum = 0;
        for (int &ele:nums) sum+=ele;
        // 也可以使用库函数一步求和
        // int sum = accumulate(nums.begin(), nums.end(), 0);

        if (sum % 2 == 1) return false;
        int target = sum / 2;
        vector dp(target+1, 0);

        for(int j=nums[0];j<=target;j++) dp[j]=nums[0];

        // 开始 一维01背包
        for(int i = 1; i < nums.size(); i++) {
            for(int j = target; j >= nums[i]; j--) { 
                dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
            }
        }
        // 集合中的元素正好可以凑成总和target
        if (dp[target] == target) return true;
        return false;
    }

觉得自己文章摘要总结的很好:

二维01背包:dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i])
一维01背包:只能物品外层,j=bagweight;j>=weight[i];j-- 
416. 分割等和子集 :正好装满sum/2, nums[i] 同时是weight和value 

你可能感兴趣的:(代码随想录一刷,算法,动态规划,leetcode,c++)