代码随想录算法训练营Day37 |01背包登场,416. 分割等和子集

今天学习了一个新的内容——01背包,应用场景是这样的,你有一个背包最多能装重量为maxweight重量的物品,你有n个物品,他们的价值分别为value[i],重量分别为weight[i],其中i为物品的下标,每件物品只能用一次,你需要保证在不超过背包所能承受的最大重量的情况下,背包内所装的最大价值是多少?这就是大名鼎鼎的01背包,那么对于这样一个问题,我们该如何思考呢,首先,还是老规矩,直接上动规五部曲

01背包问题 二维:

1.dp数组的定义和下标的含义

dp[i][j] 代表的意思就是在前 i 个物品里选择若干个物品,放在容量为j的背包里所代表的最大价值为多少,这就是dp数组的含义

2.dp数组的递推公式

我们知道对于当前的位置,你只有两种选择,那就是将物品放进背包,或者是不放进背包,那这里就有一个前提了,那就是此时的背包的容量与你的物品的重量的关系,如果说,你物品的重量大于我此时背包的容量,就说明一定是不放进去的,那就说明此时不需要做状态转移,拿这里的dp[ i ][ j ]应该等于多少呢,我们一定要明确dp数组的定义,此时背包的容量就是j,所以j是不动的,但是现在这个物品是不放进去的,所以我对应的i是不是应该减一啊,所以此时的状态转移方程应该就是dp[ i ][ j ] = dp[ i -1 ][ j ],那么另一种情况,如果说不大于背包的容量,这个物品是可以放进去的,那么我们是不是就是在放进去的价值和不放进去的价值中取一个最大值啊,所以放进去的转移方程应该怎么写呢,还是dp数组的含义,如果我放进去了,那么是不是此时我背包的容量就仅剩j - weight[ i ] 了,那这是不是就是在前i-1个物品里选择若干个物品放在j - weight[ i ]容量的背包里所代表的最大价值加上此时放入的value[ i ]啊,所以状态转移方程如下: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

3.dp数组的初始化

这里的初始化就很有讲究了,首先,就是要明确一点,我们这个dp数组的当前值是不是都是由左上和上方的值转移过来的啊,所以,我们最左边和最上面的值是必须要初始化的,首先来看最左边,当背包的容量为0时,是不是装不进任何的物品,所以全部初始化为0,当放入的物品数量为1时,我们就要看他的物品的重量是不是大于背包的容量,如果大于背包的容量,就是可以放进去的,不是的话,就是0,其他的由于都是在后面遍历的过程中会被覆盖,所以只要初始化为0就可以了

4。dp数组的遍历顺序,这里的遍历顺序是没有要求的,因为你无论是先遍历物品还是背包,你的左上部分和你的上面始终都是存在数值的,或者说是已经初始化好了,或者是已经遍历过了,所以这里是不做要求的

那么以上就是01背包二维dp数组的理论部分

01背包问题 一维:

一维呢实际上就是一个滚动数组,在二维的基础上做了一个状态压缩,实际上就是将i的那一层给压缩了,这里你就需要注意一点了,首先就是递推公式的变化,递推公式应该就是这样了,因为在二维我们知道,如果说不放入物品的话,就是直接是上面一层的状态直接复制下来,用代码表示就是这样——dp[ i ][ j ] = dp[ i -1 ][ j ],但是现在我们去除了i这一层,所以就直接写成dp[ j ] = dp[j]就可以了,如果放进去就是——dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); 还有就是初始化的操作,这里还是要再次明确dp数组的含义,和上面差不多,dp[0] = 0,其他的都是在后面遍历的时候会赋值的,所以也初始化为0就可以了,至于遍历顺序,遍历背包的顺序是不一样的,因为如果还是从前往后遍历,会把初始值给覆盖掉,所以这里要从后往前遍历,值得注意的是在内层循环遍历背包的时候,你的循环的终止条件就是大于等于我的weight[i]了,因为你要看我的递推公式有个j - weight[i] ,这样才能避免下标越界,并且小于的话也没有意义。

416. 分割等和子集:代码随想录

这道题目最重要的点是你必须要会一个转换,就是你需要在数组中找到若干个元素的和为sum的一半,这一点是非常重要的,这样你就可以将其转化为一个01背包问题,因为题目中说让你判断将这个nums数组分成两个子集,他们的和能不能相等来一个简单的数学证明,假设我选的元素的和为p,那么剩余的元素的和为为sum - p,这里的sum代表所有的元素的和,然后就是2p = sum,p = sum / 2,这样就转化为了一个01背包问题,当然,如果说sum%2!=0的话,那么我们直接返回false就可以了,然后就是dp数组以及其下标的含义,这里你还需要注意的一点就是他这里的weight数组和value数组都是nums数组,所以我们只需要在遍历完后看dp[target]是不是等于target即可,如果等于的话,就代表背包已经装满了,那么此时我直接返回true即可,相反如果不等于的话就直接返回false,这里dp数组的含义就是在背包容量为j的情况下我背包的价值是多少,递推公式只要做一点小小的改动,就是将weight数组和value数组全部改成nums数组就可以了,下面来看具体代码的实现:

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum=0;int n=nums.size();
        for(int i=0;i<n;i++) sum+=nums[i];
        if(sum%2!=0) return false;
        else {
            int target=sum/2;
            vector<int> dp(10002,0);
            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]);
                }
            }
            return dp[target]==target;
        }
    }
};

首先就是初始化,这里是全部初始化为0,然后就是遍历顺序,和上面的一维dp数组一样,就是内层循环从后往前遍历,直到j小于nums[i]的时候为止,最后返回是否相等即可,这题我也是没有写出来,思路我都想明白了,但是就是不知道代码应该怎么写,可能是今天刚刚接触这个新的知识吧,我现在觉得我已经差不多清楚01背包的大致过程了,但是一些细节上的点,还是不是很明白

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值