力扣动态规划专题(二)01背包 416. 分割等和子集 1049.最后一块石头的重量II 494. 目标和 474. 一和零 步骤及C++实现

01背包

在这里插入图片描述
完全背包的物品数量是无限的,01背包的物品数量只有一个。

有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
背包最大重量为4,问背包能背的物品最大价值是多少?物品为:

重量价值
物品0115
物品1320
物品2430

二维dp数组

  1. 确定dp数组以及下标的含义:二维数组dp[i][j],表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少,如
背包重量j01234
物品0
物品1
物品2
  1. 确定递推公式,有两个方向推出来dp[i][j]:
  • 不放物品i:由dp[i - 1][j]推出,即背包容量为j,里面不放物品i的最大价值,此时dp[i][j]=dp[i - 1][j]。意味着,此时物品i的重量大于背包j的重量时,物品i无法放进背包中,所以背包内的价值依然和前面相同。
  • 放物品i:由dp[i - 1][j - weight[i]]推出,dp[i - 1][j - weight[i]] 为背包容量为j - weight[i]的时候不放物品i的最大价值,此时dp[i][j]=dp[i - 1][j - weight[i]] + value[i],物品i的价值为value[i]。意味着,物品i的重量小于背包j的重量时,背包放物品i得到的最大价值。
  • 递归公式: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
  1. dp数组如何初始化
    情况1:j=0时,dp[i][0]=0,此时背包容量j为0,无论选取什么物品,背包价值总和为0
    情况2:i=0时,dp[0][j],表示存放编号0的物品的时候,各个容量的背包所能存放的最大价值。
    当 j < weight[0]时,dp[0][j]=0,因为背包容量比编号0的物品重量还小;
    当 j >= weight[0]时,dp[0][j]=value[0],因为背包容量放足够放编号0物品。
背包重量j01234
物品0015151515
物品10
物品20
  1. 确定遍历顺序
    先遍历物品,或者先遍历背包都可以

  2. C++实现

void test_2_wei_bag_problem1() {
    vector<int> weight = {1, 3, 4};
    vector<int> value = {15, 20, 30};
    int bagweight = 4;

    // 二维数组
    vector<vector<int>> dp(weight.size(), vector<int>(bagweight + 1, 0));

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

    // weight数组的大小 就是物品个数
    for(int i = 1; i < weight.size(); i++) { // 遍历物品
        for(int j = 0; 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]);

        }
    }
    /*
    // weight数组的大小 就是物品个数
	for(int j = 0; j <= bagweight; j++) { // 遍历背包容量
	    for(int i = 1; i < weight.size(); i++) { // 遍历物品
	        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() {
    test_2_wei_bag_problem1();
}

一维dp数组 滚动数组

在二维数组的基础上,把上一层结果覆盖在当前层,即dp[i - 1]那一层拷贝到dp[i]上

  1. 确定dp数组以及下标的含义:一维数组dp[j],容量为j的背包,所背的物品价值可以最大为dp[j]
背包重量j01234
物品0
物品1
物品2
  1. 确定递推公式,有两个方向推出来dp[j]:
    dp[j]可以通过dp[j - weight[i]]推导出来,dp[j - weight[i]]表示容量为j - weight[i]的背包所背的最大价值。

dp[j - weight[i]] + value[i] 表示 容量为 j - 物品i重量 的背包 加上 物品i的价值。(也就是容量为j的背包,放入物品i了之后的价值即:dp[j])

  • 不放物品i:由dp[j]本身推出,相当于二维dp数组的dp[i - 1][j]。此时物品i的重量大于背包j的重量时,物品i无法放进背包中,背包内的价值不变。
  • 放物品i:由dp[j - weight[i]] + value[i]推出,物品i的价值为value[i],dp[j - weight[i]]表示容量为j - weight[i]的背包所背的最大价值。物品i的重量小于背包j的重量时,背包放物品i得到的最大价值。
  • 递归公式: dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
  1. dp数组如何初始化
    情况1:j=0时,dp[0]=0,此时背包容量j为0,无论选取什么物品,背包价值总和为0
    情况2:j≠0时,dp[j]会被覆盖更新。
背包重量j01234
物品0015151515
物品10
物品20
  1. 确定遍历顺序
    二维dp遍历的时候,背包容量是从小到大,先遍历物品或者先遍历背包都可以,正序遍历
    一维dp遍历的时候,背包是从大到小,只能先遍历物品再遍历背包容量,倒序遍历背包容量,都是是为了保证物品i只被放入一次

  2. C++实现

void test_1_wei_bag_problem() {
    vector<int> weight = {1, 3, 4};
    vector<int> value = {15, 20, 30};
    int bagWeight = 4;

    // 初始化
    vector<int> dp(bagWeight + 1, 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]);
        }
    }
    cout << dp[bagWeight] << endl;
}

int main() {
    test_1_wei_bag_problem();
}

416. 分割等和子集

在这里插入图片描述
背包问题确定

元素只能使用一次,不可重复放入,01背包
背包的体积为sum / 2
背包要放入的商品(集合里的元素)重量为 元素的数值,价值也为元素的数值
背包如果正好装满,说明找到了总和为 sum / 2 的子集。

步骤

  1. 确定dp数组以及下标的含义
    题目的每一个元素的数值既是重量,也是价值。
    那么dp[j]表示背包总容量(所能装的总重量)是j,放进物品后,背的最大重量为dp[j]。
    背包容量为target, dp[target]就是装满 背包之后的重量,所以 当 dp[target] == target 的时候,背包就装满了。

  2. 确定递推公式
    物品i的重量是nums[i],其价值也是nums[i],那么递推公式:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);

  3. dp数组如何初始化
    j=0时,dp[0]=0,j≠0时,dp[j]会被覆盖更新。
    要注意的是,如果题目给的价值都是正整数,那么非0下标都初始化为0;如果给的价值有负数,那么非0下标就要初始化为负无穷。

  4. 确定遍历顺序
    使用一维dp数组,只能先遍历物品,即物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒序遍历

  5. 举例推导dp数组
    dp[j]的数值一定是小于等于j的,如果dp[j] == j 说明,集合中的子集总和正好可以凑成总和j。用例1,输入[1,5,11,5] 为例,target = (1+5+11+5) / 2 = 11

下标i01234567891011
01111566661011
  1. C++实现
class Solution {
public:
    bool canPartition(vector<int>& nums) {
        
        // 1 <= nums.length <= 200,1 <= nums[i] <= 100
        //dp数组总和不会大于20000,背包最大只需要其中一半,所以10001大小就可以了
        vector<int> dp(10001, 0);//dp数组初始化

        int sum = 0;
        for(int i=0; i<nums.size(); i++)
        {
            sum += nums[i];
        }
        if(sum % 2 ==1) return false;
        int target = sum / 2;

        //01背包
        for(int i=0; i<nums.size(); i++)
        {
            for(int j = target; j>=nums[i]; j--)
            {
                dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
            }
        }
        if(dp[target] == target) return true;
        return false;
    }
};

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

在这里插入图片描述
步骤

  1. 确定dp数组以及下标的含义
    物品的重量为stones[i],物品的价值也为stones[i]。
    dp[j]表示容量(重量)为j的背包,最多可以背最大重量为dp[j],最多可以装的价值为 dp[j] = 最多可以背的重量为dp[j]

  2. 确定递推公式
    物品i的重量和价值都是stones[i],那么递推公式:dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);

  3. dp数组如何初始化
    在这里插入图片描述
    target=最大重量的一半,最大重量100*30=3000
    j=0时,dp[0]=0,重量不可能是负数。

  4. 确定遍历顺序
    使用一维dp数组,只能先遍历物品,即物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒序遍历

  5. 举例推导dp数组
    dp[j]的数值一定是小于等于j的,如果dp[j] == j 说明,集合中的子集总和正好可以凑成总和j。输入[2,4,1,1],target = (2 + 4 + 1 + 1)/2 = 4

  6. C++实现

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;
        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]);
            }
        }
        return sum - dp[target] - dp[target];
    }
};

494. 目标和

在这里插入图片描述
加法总和 + 减法总和 = sum,加法总和为x,那么 x - (sum - x) = target,left = (target + sum)/2 。

步骤

  1. 确定dp数组以及下标的含义
    一维dp数组,dp[j] 表示:填满j(包括j)这么大容积的包,有dp[j]种方法
    二维dp数组,dp[i][j]表示:使用 下标为[0, i]的nums[i]能够凑满j(包括j)这么大容量的包,有dp[i][j]种方法。

  2. 确定递推公式
    nums[i],凑成dp[j]就有dp[j - nums[i]] 种方法。
    求组合类问题的公式,都类似:dp[j] += dp[j - nums[i]];

  3. dp数组如何初始化
    初始化 dp[0] 为 1
    情况剔除:如果target > sum,无解;如果(target+sum) % 2 =1,无解

  4. 确定遍历顺序
    使用一维dp数组,只能先遍历物品,即物品遍历在外循环,遍历背包在内循环,且内循环倒序遍历,即nums在外循环,target在内循环,且内循环倒序。

  5. 举例推导dp数组
    nums: [1, 1, 1, 1, 1], target: 3
    bagSize = (target + sum) / 2 = (3 + 5) / 2 = 4

  6. C++实现

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int sum = 0;
        for(int i=0; i<nums.size(); i++) sum += nums[i];
        //情况剔除
        if(abs(target) > sum) return 0;
        if((target + sum) % 2 ==1) return 0;
        int bagsize = (target + sum) / 2;
        
        //dp数组 初始化
        vector<int> dp(bagsize + 1, 0);
        dp[0] = 1;

        //dp数组更新 先物品后背包 背包倒序
        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. 一和零

在这里插入图片描述
strs 数组里的元素就是物品,每个物品都是一个;m 和 n相当于是一个背包,两个维度的背包。

步骤

  1. 确定dp数组以及下标的含义
    二维dp数组,dp[i][j]:最多有i个0和j个1的strs的最大子集的大小

  2. 确定递推公式

  • dp[i][j] 可以由strs里的前一个字符串推导,strs里的字符串有zeronum个0,onenum个1,那么dp[i][j] 就可以是 dp[i - zeroNum][j - oneNum] + 1。
  • 遍历的过程中,取dp[i][j]的最大值。
  • 递推公式:dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
  • 01背包的递推公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);,字符串的zeronum和onenum相当于物品的重量weight[i],字符串本身的个数相当于物品的价值value[i]
  1. dp数组如何初始化
    初始化 dp[0] 为0,保证递推的时候dp[i][j]不会被初始值覆盖

  2. 确定遍历顺序
    使用一维dp数组,只能先遍历物品,即物品遍历在外循环,遍历背包在内循环,且内循环倒序遍历,即strs里的字符串在外循环,m和n在内循环,且内循环倒序。

  3. 举例推导dp数组
    以输入:[“10”,“0001”,“111001”,“1”,“0”],m = 3,n = 3为例,

  4. C++实现
    注意字符串的遍历

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        //dp数组 默认初始化为0
        vector<vector<int>> dp(m+1, vector<int>(n+1, 0));
        for(string str : strs)//遍历物品
        {
            //统计数量
            int onenum = 0, zeronum = 0;
            for(char c : str)
            {
                if(c == '0') zeronum++;
                else onenum++;
            }
            //遍历背包 倒序遍历 两个维度的背包
            for(int i=m; i>=zeronum; i--)
            {
                for(int j=n; j>=onenum; j--)
                {
                    dp[i][j] = max(dp[i][j], dp[i-zeronum][j-onenum] + 1);
                }
            }
        }
        return dp[m][n];
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值