416. 分割等和子集

在这里插入图片描述
子集问题,一定要考虑到背包问题,也就是动态规划。
在这里插入图片描述
此处的dp[i][j]的含义就是要装满
在这里插入图片描述
最后还可以进行状态压缩,简称状压dp
在这里插入图片描述
这句话很重要,如果你要用多次的话,就不要反向遍历。相当于对每一层反向填表。
在这里插入图片描述
最后主要是掌握!二维dp的使用

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        //只要是取或者不取就直接背包问题
        int sum = 0;
        for(auto x:nums) sum+=x;
        if(sum%2!=0) return false;//如果数组和是奇数就一定不能分成两个相等的子集
        //这其实也就是分成了两个背包,一个背包能装满sum/2,另一个背包一定可以
        //dp[i][j]代表了对于前i个物品,当背包容量为j时恰好可以装满为true;不能恰好就false
        //比如说,如果 dp[4][9] = true,其含义为:对于容量为 9 的背包,若只是用前 4 个物品,可以有一种方法把背包恰好装满。所以我们最后要求的就是dp[n][sum/2]
        //初始化dp[i][0]无需装东西,都是true;dp[0][i]没东西装,都是false
        // vector<vector<bool>> dp(nums.size()+1,vector<bool>(sum/2+1));
        // for(int i = 0; i <= nums.size(); ++i){
        //     dp[i][0] = true;
        // }
        // //状态转移方程是根据选择来的。dp[i][j]如果没有选择第i个物品,那么dp[i][j] = dp[i-1][j];就是继承上一个状态
        // //如果选择了dp[i][j] = dp[i-1][j-nums[i]];也就是j-nums[i]的重量可以恰好装满,那j的重量在加上nums[i]后也可以恰好装满,但是i从1开始,所以是nums[i-1]
        // for(int i = 1; i <= nums.size(); ++i){
        //     for(int j = 1; j <= sum/2; ++j){
        //         //此时考虑背包容量是否足够,那只能不装i了
        //         if(j - nums[i-1] < 0){
        //             dp[i][j] = dp[i-1][j];
        //         }
        //         else{
        //             //取或者不取只要有一种情况是对的,那就是对的
        //             dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i-1]];
        //         }
        //     }
        // }
        // return dp[nums.size()][sum/2];

        //我们发现dp[i][..]之和dp[i-1][..]的有关系,所以我们采用状压dp,只要记录dp[i-1][...]的值即可
        vector<bool> dp(sum/2+1,false);
        dp[0] = true;//如果取总量为0的话,不管怎么取都可以
        //dp[i]记录的是取到总量为i时,是否可以正常取到
        for(int i = 0; i < nums.size(); ++i){
            for(int j = sum/2; j >= 0; --j){
                if(j - nums[i] >= 0){
                    //如果可以取就是两种情况
                    dp[j] = dp[j] || dp[j-nums[i]];
                }
            }
        }
        return dp[sum/2];
    }
};

可以dfs,也可以dp。
就是01背包,取或者不取,直接先上二维dp,dp[i][j]代表的是前i个数字是否可以组成总和j。
状态转移方程:如果取了当前物品会超过重量j,就让dp[i][j]继承dp[i-1][j]就是不取这个物品
如果没超过,就判断取当前这个物品或者不取有一个是对的就行了。
空间压缩:状压dp,只和上一层有关系,但是要从后往前,因为不可以重复取,只要上一层取或者没取的结果。

class Solution {
public:
    // bool dfs(int index, vector<int>& nums, int tmp,int sum){
    //     //不用考虑越界,用for循环控制
    //     if(tmp > sum) return false;
    //     if(tmp == sum) return true;
    //     for(int i = index; i < nums.size();++i){
    //         if(dfs(i+1,nums,tmp+nums[i],sum)) return true;
    //     }
    //     return false;
    // }
    bool canPartition(vector<int>& nums) {
        //分割子集问题
        int sum = 0;
        for(int x : nums){
            sum += x;
        }
        if(sum % 2 != 0) return false;
        //先试试dfs,就是当前数字选或者不选,当越界就false,和为sum/2就true
        // return dfs(0,nums,0,sum/2);  

        //背包问题,就是取或者不取的问题,背包问题一定是二维dp,先写出二维,再压缩
        //dp[i][j]=true代表的是取前i件物品有一种情况可以把重量为j的背包刚好填满,最后求的是dp[n][sum/2]
        //状态转移方程:如果取了当前物品会超过重量j,就让dp[i][j]继承dp[i-1][j]就是不取这个物品
        //如果没超过,就判断取当前这个物品或者不取有一个是对的就行了
        //就是dp[i-1][j] || dp[i-1][j-nums[i-1]]
        //basecase:
        // vector<vector<bool>> dp(nums.size()+1,vector<int>(sum/2+1));
        // //用来装大小为0的背包都是true
        // for(int i = 0; i <= nums.size(); ++i){
        //     dp[i][0] = true;
        // }
        // for(int i = 1; i <= nums.size(); ++i){
        //     for(int j = 1; j <= sum/2; ++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]];
        //         }
        //     }
        // }
        // return dp[nums.size()][sum/2];

        //状压dp,只和上一层有关系,但是要从后往前
        vector<bool> dp(sum/2+1);
        //basecase
        dp[0] = true;
        for(int i =1; i <= nums.size(); ++i){
            for(int j = sum/2; j >= 1; --j){
                if(j < nums[i-1]){
                    dp[j] = dp[j];
                }
                else{
                    dp[j] = dp[j] || dp[j-nums[i-1]];
                }
            }
        }
        return dp[sum/2];
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值