第九章 动态规划 part05(代码随想录)

纯01背包:装满背包最大价值

01背包在不同层面的应用:

分割等和子集:能不能装满背包

最右一块石头的重量:背包能装多少装多少

目标和:多少种方式把背包装满

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

想法:把石头尽可能地分成2堆,这2堆它的重量相似的话,相撞后所剩值为最小值。

例如:23分成12和11

本题物品的重量为stones[i],物品的价值也为stones[i]。

本题中,石头的重量是 stones[i],石头的价值也是 stones[i] ,可以 “最多可以装的价值为 dp[j]” == “最多可以背的重量为dp[j]

1. 确定dp[i][j] dp数组以及下标的含义

dp[j]表示容量(这里说容量更形象,其实就是重量)为j的背包,最多可以背最大重量为dp[j]。
2. 确定递推公式

dp[j] = num(dp[j], dp[j - stones[i]] + stones[i]);

3. dp数组如何初始化

dp[0] = 0

vector<int> dp(1501, 0);

4. 确定遍历顺序

如果使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒序遍历

for (int i = 0; i < stones.size(); i++) { // 遍历物品
    for (int j = target; j >= stones[i]; j--) { // 遍历背包
        dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);
    }
}

5. 打印dp数组

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

class Solution {
public:
    int lastStoneWeightII(vector<int>& stones) {
        vector<int> dp(1501, 0);
        int sum=0;
        for (int i = 0; i < stones.size(); i++) {
            sum+=stones[i];
        }
        int target = sum /2; // 向下取整 例子:5/2=2不等于3
        dp[0] = 0;
        // 尽量放满sum/2重量
        for (int i=0; i<stones.size(); i++){ // 物品
            for (int j=target; j>= stones[i];j--){ // 如果背包容量比遍历石头重量还小,遍历无意义
                dp[j] = max(dp[j], dp[j-stones[i]]+stones[i]); 
            }
        }
        // 另一堆石头重量 sum - dp[target] 一定大于dp[target]
        return sum - dp[target] - dp[target];
    }
};

 494. 目标和 

把集合分成放加法(left)、放减法两个集合(right)

sum、target是确定的

left + right = sum (1)

left - right = target (2)

把(1)带入(2):

left- (sum - left) = target

left = (target + sum)/2 (正数的集合)

可以推出负数的集合 right = sum - left 

如果 (target + sum)/2 不能整除,里面没组合凑不成target,例如:

7/2

target=2
413
321
23-1
14-3
 

大家重点理解 递推公式:dp[j] += dp[j - nums[i]]

1. 确定dp[i][j] dp数组以及下标的含义

dp[i][j]:使用 下标为[0, i]的nums[i]能够凑满j(包括j)这么大容量的包,有dp[i][j]种方法


2. 确定递推公式

在不考虑nums[i]的情况下,装满容量为j-nums[i]的背包,有dp[j-nums[i]]种方法。

那么只要找到nums[i],凑成dp[j]就有dp[j-nums[i]]种方法。

假设nums[i]=2,凑成dp[5]就有dp[3]种方法。

只要找到一个2,则有dp[3]中方法可以装满容量为3的背包,相应地就有dp[3]种方法可以装满容量为5的背包。

已有物品1 dp[4]种 凑成dp[5]

已有物品2 dp[3]种 凑成dp[5]

已有物品3 dp[2]种 凑成dp[5]

已有物品4 dp[1]种 凑成dp[5]

已有物品5 dp[0]种 凑成dp[5]

装满容量为5背包的所有方法:dp[4]+dp[3]+dp[2]+dp[1]+dp[0]

dp[j-nums[i]]进行累加最后就是我们的dp[j]

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

3. dp数组如何初始化

装满容量为0的背包有一种方法,就是装0件物品。

dp[0] = 1

其他非零下标对应的数组也应该初始化为0,保证dp[j]的初始值是0,才能正确地由dp[j-nums[i]]推导出来。

4. 确定遍历顺序

每个元素只能用一次,01背包

for (i = 0; i < nums.size(); i++) { // 物品
    for (j = bagsize; j >= nums[i]; j--) { // 背包

    }
}

5. 打印dp数组

输入:nums: [1, 1, 1, 1, 1], S: 3

bagSize = (S + sum) / 2 = (3 + 5) / 2 = 4

dp数组状态变化如下:

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int sum = accumulate(nums.begin(), nums.end(), 0); // 库函数求和

        if (abs(target) > sum) return 0;  //如果target过大 非负整数数组的sum将无法满足
        if ((target+sum)%2 == 1) return 0;
        
        int bagsize = (target + sum) / 2; // left是bagsize
        vector<int> dp(bagsize+1, 0);
        dp[0] = 1;
        for (int i=0; i<nums.size(); i++){ // 物品
            for (int j=bagsize; j>=nums[i]; j--){
                dp[j] += dp[j-nums[i]];
            }
        }
        return dp[bagsize];
    }
};

 474.一和零  

重量有a和b两个维度

装满背包最多有多少物品、m、n 三个变量

1. 确定dp[i][j] dp数组以及下标的含义

二维数组 dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]。

2. 确定递推公式

最终要求的结果:dp[m][n] 背包最大容量是装最多m个0,n个1

strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3 物品有x个0,y个1

(1)减物品重量:

背包重量(目前的i个0,j个1)减去 物品重量(物品里面的x个0,y个1)

dp[i-x][j-y]

(2)放物品:

加上物品价值

+1

遍历各个物品 背最大的物品

max()

dp[i][j] = max(dp[i-x][j-y]+1, dp[i][j])

3. dp数组如何初始化

dp[0][0]=0

因为物品价值不会是负数,初始为0,保证递推的时候dp[i][j]不会被初始值覆盖。

4. 确定遍历顺序

for (string str : strs) { // 遍历物品
    int oneNum = 0, zeroNum = 0;
    // 把有多少个0,多少个1取出来(物品总量)
    for (char c : str) {
        if (c == '0') zeroNum++;
        else oneNum++;
    }
    // m, zeroNum代表0的个数
    // 遍历背包容量且从后向前遍历!下面2个for循环可以颠倒
    for (int i = m; i >= zeroNum; i--) { // 遍历0的个数 比物品的0的个数都小就无遍历意义
        for (int j = n; j >= oneNum; j--) { // 遍历1的个数
            dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
        }
    }
}

5. 打印dp数组

474.一和零

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        vector<vector<int>> dp(m+1, vector<int>(n+1, 0));

        for (string str : strs) { // 遍历物品
            // x、m:zeroNum y、n:oneNum
            int x = 0, y = 0;
            for (char c : str) {
                if (c == '0') x++; // 统计字符串中0的个数
                else y++; // 统计字符串中1的个数
            }

            for (int i = m; i >= x; i--) { // 遍历背包 遍历0的个数 比物品的0的个数都小就无遍历意义
                for (int j = n; j >= y; j--){ // 遍历1的个数 比物品的1的个数都小就无遍历意义
                    dp[i][j] = max(dp[i][j], dp[i-x][j-y] + 1);
                }
            }

        }
        return dp[m][n];
    }
};

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值