leetcode——背包问题

初识背包问题

算法题也做了有200多题了,终于准备刷一波背包问题,花了点时间写了一题中等难度的,其实就是动态规划

按题目类型来分有三种,完全背包0-1背包

0-1背包就是指货物只有两种状态,装或不装,不能多装,也不能拆一半装

完全背包就是指同种类的货物是无限的,可以无限重复选取

动态规划:

今天写的是子集背包,所谓子集背包,就是直接或间接给出一个目标值target,然后让你在货物中选择,看看能不能正好凑出一堆价值为target的子集,所以从货物的选取上来看,是属于0-1背包问题的一种

416. 分割等和子集

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

示例 1:

输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。

来源:力扣(LeetCode)

这一题让我们分割子集,分成元素和相等的两部分,这就可以看成是在货物中找和为总和/2的子集的问题,就是子集背包问题

思路就是动态规划,建立一个二维的dp数组

dp的意义

dp[i][j]就表示前i个货物在背包容量为j时能不能有一个子集刚好凑够target,能就是true,不能就是false;
因为vector< bool >的特殊性,这里用char代替,
然我们我们从下往上推即可

状态方程

对于dp[i][j],我们先判断一下当前的货物是否能装入背包,如果当前货物重量比背包容量大,那么很明显时不能装进去了,此时应该怎么赋值呢?想一下,虽然最高货物无法装入背包,但如果不算最高获取,之前的货物能凑够的话,那现在也还是能凑够的

比如	1, 2, 3, 4 

假如我要凑3出来,很明显,在4之前就有两种方法能凑够了,{ 1,2 } 和 { 3 },那我们现在有4个货物 ,虽然第四个不能进背包,但是我们可以直接从前面选啊,还是可以选择{ 1,2 } 和 { 3 },

所以如果当前货物不能进背包,我们就只能从前面的货物中看看能不能凑够了

即dp[i][j] = dp[i-1][j]

如果能装进来,上面的条件依旧能用,但就算前面的凑不够,也没事,我们还有机会,毕竟最新的货物nums[i-1]来了,我们看看当前货物中能不能凑够 j - nums[i-1],只要能凑够,那只要再加上最新的货物,不就刚好凑够 j 了嘛

即dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i-1]]

ps:上面的nums[i-1]要-1时因为下标从0开始

初值

对于二维dp数组,初值就是两边界的值,而对于这一题,初值还是很容易决定的,不用耍花招,

对于货物为0的情况,不管背包容量为多少(为0的除外),都不可能凑够吧

而对于背包容量为0的情况,不管货物有多少,只要不装就行了

我默认y轴为货物数量,x轴为背包容量。于是左边界为true,代表背包容量为0的情况,上边界为false

特例

如果总货物重量为奇数,那么就不可能凑得出来吧,题目也说了货物重量都为正整数,两相同正整数相加怎么可能为奇数啊

优化?

先不说状态压缩,先说说其他能优化的地方吧
0:对于C++来说别用vector< bool >,省空间但浪费时间
1:dp数组可使用静态数组,但是大部分编译器不允许用变量构造数组,硬要创建二维的静态数组很麻烦
2:网上看可以用deque< bool >代替vector< bool >但不知道为啥,这一题用deque比vector还慢,差点就超时了
3:可以在每次赋值完dp[?][target]时判断是否为真,是就可以直接返回了,反正题目只问能不能凑够targe,我管你货物的取值范围是多少,反正凑够就行了,或许能叫剪枝吧?哈哈,排除了后续无谓的计算
在这里插入图片描述
静态数组:可惜大部分编译器并不能这样简单的建立二维数组,另外,因为char和bool都为1字节,使用哪个都没差
在这里插入图片描述

代码:

class Solution {
   
public:
    bool canPartition(vector<int>& nums) {
   
        int sum = 0;
        for(int&num:nums)
        sum+=num;
        if(sum%2==1)return false;//特例
        sum/=2;
        vector<vector<char>>dp(nums.size()+1,vector<char>(sum+1,false));//建立dp数组
        //bool dp[nums.size()+1][sum+1];
        //fill_n(&dp[0][0],(nums.size()+1)*(sum+1), false);
        for(int idx = 0;idx<=nums.size();++idx)
        dp[idx][0] = true;//初值
        for(int i = 1;i<=nums.size();++i)
        {
   
            for(int j = 1;j<=sum;++j)
            {
   
                if(j < nums[i-1])//装不下
                {
   
                    dp[i][j] = dp[i-1][j];
                }
                else
                {
   //装得下
                    dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i-1]];
                }
            }
            if(dp[i][sum])return true;
        }
        return false;
    }
};

状态压缩?

土法:

咱们今天先不搞高级的,来点土的,高级的状态压缩日后在说,

我们可以通过上述代码发现,dp数组的每一行数据只与上一行有关系,与前面的都无关,那完全可以只用两个一维数组解决
用一个临时dp储存上次的数据,然后对当前dp赋值的时候,就使用临时dp的数据,到最后再更新临时dp,就是两个数组滚动着更新,和一维dp压缩成2个变量是一个道理

代码:

class Solution {
   
public:
    bool canPartition(vector<int>
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

timathy33

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值