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

# 二维dp数组,01背包 

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

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

2. 

gpt 解决我的困惑 

 

3. 

 另外:

当 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<int> &weight, vector<int> &value, int &bagweight) {
    

    // 二维数组+初始化1
    vector<vector<int>> dp(weight.size(), vector<int>(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<int> weight = {1, 3, 4};
    vector<int> value = {15, 20, 30};
    int bagweight = 4;

    test_2d_bag_problem1(weight,value,bagweight);
}

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

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

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

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

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

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

    // 二维数组+初始化1
    vector<int> 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<int> weight = {1, 3, 4};
    vector<int> value = {15, 20, 30};
    int bagweight = 4;

    test_1d_bag_problem1(weight,value,bagweight);
}

面试的问法:

#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<int>& 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<int> 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 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第二十二算法训练营主要涵盖了Leetcode题目中的三道题目,分别是Leetcode 28 "Find the Index of the First Occurrence in a String",Leetcode 977 "有序数组的平方",和Leetcode 209 "长度最小的子数组"。 首先是Leetcode 28题,题目要求在给定的字符串中找到第一个出现的字符的索引。思路是使用双指针来遍历字符串,一个指向字符串的开头,另一个指向字符串的结尾。通过比较两个指针所指向的字符是否相等来判断是否找到了第一个出现的字符。具体实现的代码如下: ```python def findIndex(self, s: str) -> int: left = 0 right = len(s) - 1 while left <= right: if s[left == s[right]: return left left += 1 right -= 1 return -1 ``` 接下来是Leetcode 977题,题目要求对给定的有序数组中的元素进行平方,并按照非递减的顺序返回结果。这里由于数组已经是有序的,所以可以使用双指针的方法来解决问题。一个指针指向数组的开头,另一个指针指向数组的末尾。通过比较两个指针所指向的元素的绝对值的大小来确定哪个元素的平方应该放在结果数组的末尾。具体实现的代码如下: ```python def sortedSquares(self, nums: List[int]) -> List[int]: left = 0 right = len(nums) - 1 ans = [] while left <= right: if abs(nums[left]) >= abs(nums[right]): ans.append(nums[left ** 2) left += 1 else: ans.append(nums[right ** 2) right -= 1 return ans[::-1] ``` 最后是Leetcode 209题,题目要求在给定的数组中找到长度最小的子数组,
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值