代码随想录刷题攻略---动态规划2---01背包

例题1

小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。他需要带一些研究材料,但是他的行李箱空间有限。这些研究材料包括实验设备、文献资料和实验样本等等,它们各自占据不同的空间,并且具有不同的价值。 

小明的行李空间为 N,问小明应该如何抉择,才能携带最大价值的研究材料,每种研究材料只能选择一次,并且只有选与不选两种选择,不能进行切割。

动规五部曲

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

dp[i][j] 的含义:在下标 [0~i] 的物品中任取,装进容量为 j 的背包,所获得的最大价值为 dp[i][j].

官方给的图:

2.确定dp数组的递推式

由1,当前dp[i][j]的状态是由上一个 dp[i-1][j] 推导来的,dp[i-1][j] 已经做好了决策,那么到dp[i][j]的时候,我们可以选择放当前下标为 i 的物品,也可以选择不放。

如放,dp[i][j]=dp[i][j-weight[j]]+value[i]

如不放,dp[i][j]=dp[i-1][j]

此时我们要求dp最大值,故 dp[i][j]=max(dp[i-1][j],dp[i][j-weight[j]]+value[i])

3.初始化dp数组

对于dp二维数组,我们通常将i=0,j=0的一行一列进行初始化

当i=0时,dp[0][j]表示从下标为0的物体中取物装进背包。这时若  j>= weight[0] ,则放入背包,反之dp [0][j]都为0

当j=0时,背包容量为0,此时dp[i][0]都为0,放不下任何物品。

4.确定遍历顺序

由递推式可知,有两个遍历的维度:物品与背包重量,先遍历哪个都行,因为dp[i][j]需要靠dp[i-1][j]推导,那么在这里先遍历物品i。

5.举例推导dp数组

做动态规划的题目,最好的过程就是自己在纸上举一个例子把对应的dp数组的数值推导一下,然后在动手写代码!

code

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]);

        }
    }

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

int main() {
    test_2_wei_bag_problem1();
}

 例题2

给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

注意:每个数组中的元素不会超过 100 数组的大小不会超过 200

动规五部曲

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

如题,若集合元素的和为sum,分割为两个子集,那么如果其中一个子集的集合元素和为sum/2,另一个子集元素的和自然而然也为sum/2,所以我们只需验证,集合中能否找出集合元素元素和为sum/2的序列,也就是一个容量为sum/2的背包是否能被填满 的0-1背包问题

dp[j]的含义:容量为j的背包,所能装的物品的最大价值为dp[j]。这样,当dp[j] == sum/2时,就证明true。

2.确定dp数组的递推式

根据dp[j]的含义,dp[j]每次在 放当前物品不放当前物品 中取最大值,而物品的重量和价值是同一个表达式,即nums[i]。故dp[j] = max(dp[j], dp[j - weight[i]] + values[i])

3.初始化dp数组

dp[0]=0,当j=0时,背包容量为0,放不下任何物品。

如果题目给的价值都是正整数那么非0下标都初始化为0就可以了,如果题目给的价值有负数,那么非0下标就要初始化为负无穷。

4.确定遍历顺序

二维dp遍历的时候,背包容量是从小到大,而一维dp遍历的时候,背包是从大到小。因为如果正序遍历背包的话,背包从小到大的过程中可能会放入多次的i相同的物品,导致一个背包放入了多次的相同物品。

那么可以先遍历背包再遍历物品吗?不可以。如果遍历背包容量放在上一层,那么每个dp[j]就只会放入一个物品,即:背包里只放入了一个物品

倒序遍历的原因是,本质上还是一个对二维数组的遍历,并且右下角的值依赖上一层左上角的值因此需要保证左边的值仍然是上一层的,从右向左覆盖

5.举例推导dp数组

code 

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int n=nums.size();
        int sum=0;
        for(int i=0;i<n;i++)
            sum += nums[i];
            if(sum %2 != 0)return false;
        //sort(nums.begin(),nums.end());
        bool res=canfull(nums,nums,sum/2);
        return res;
    }

    bool canfull(vector<int> weight,vector<int> value,int size)
    {
        bool res=false;
        vector<int> dp(10001,0);
        dp[0]=0;
        for(int i=0;i<value.size();i++)
        {
            for(int j=size;j>= weight[i];j--)
                dp[j] = max(dp[j],dp[j-weight[i]]+value[i]);
        }
        if(dp[size] == size) 
        res=true;
        return res;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值