416. 分割等和子集

题目描述:

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

注意:

每个数组中的元素不会超过 100
数组的大小不会超过 200
示例 1:

输入: [1, 5, 11, 5]

输出: true

解释: 数组可以分割成 [1, 5, 5] 和 [11].
 

示例 2:

输入: [1, 2, 3, 5]

输出: false

解释: 数组不能分割成两个元素和相等的子集.

思想:0-1背包

做这道题需要做这样一个等价转换:是否可以从这个数组中挑选出一些正整数,使得这些数的和等于整个数组元素的和的一半。前提条件是:数组的和一定得是偶数,即数组的和一定得被 22 整除,这一点是特判。

作为“0-1 背包问题”,它的特点是:“每个数只能用一次”。思路是:物品一个一个选,容量也一点一点放大考虑(这一点是“动态规划”的思想,特别重要)。

 

详细分析参考:https://leetcode-cn.com/problems/partition-equal-subset-sum/solution/0-1-bei-bao-wen-ti-xiang-jie-zhen-dui-ben-ti-de-yo/

代码1:使用二维dp

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int len = nums.size();
        if(len == 0)
            return false;
        int sum = 0;
        for(int i=0;i<nums.size();i++)
            sum += nums[i];
        if(sum & 1 == 1)
            return false;
        int target = sum / 2;
        vector<vector<int>> dp(len,vector<int>(target+1));
        dp[0][0] = true;  //容量为 0 的时候,即 dp[i][0] 按照本意来说,应该设置为 false ,但是注意到状态转移方程,
                //j - nums[i] == 0 成立的时候,根据上面分析,就说明单独的 nums[i] 这个数就恰好能够在
                //被分割为单独的一组,其余的数分割成为另外一组,因此把初始化的 dp[i][0] 设置成为 true
                //在代码运行层面是完全没有问题的
if(nums[0] <= target) dp[0][nums[0]] = true; for(int i = 1;i < len;i++) for(int j = 0;j <= target;j++){ dp[i][j] = dp[i-1][j]; if(nums[i] == j){ dp[i][j] = true; continue; } if(nums[i]<=j) dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i]]; if(dp[i][target]) //剪枝操作,or 的结果只要为真,表格下面所有的值都为真,因此在填表的时候,只要表格的最后一列是 true,代码就可以结束 return true; } return dp[len-1][target]; } };

代码2:使用一维dp

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int len = nums.size();
        if(len == 0)
            return false;
        int sum = 0;
        for(int i=0;i<nums.size();i++)
            sum += nums[i];
        if((sum & 1) == 1)
            return false;
        int target = sum / 2;
        vector<int> dp(target+1);   //在“填表格”的时候,当前行只参考了上一行的值,因此状态数组可以只设置 2 行,
                                    //使用“滚动数组”的技巧“填表格”即可;实际上连“滚动数组”都不必,                                   
                                    //在“填表格”的时候,当前行总是参考了它上面一行 “头顶上” 那个位置和“左上角”某个位置的值。
                                    //因此可以只开一个一维数组,从后向前依次填表即可。

        dp[0] = true; 
        if(nums[0] <= target)
            dp[nums[0]] = true;
        for(int i = 1;i < len;i++)
            for(int j = target;nums[i] <= j;j--){   //从后向前遍历j,当j的值小于nums[i]时,停止该轮循环
                if(dp[target] == true){
                    return true;
                }
                dp[j] = dp[j] || dp[j-nums[i]];
            }
        return dp[target];
    }
};

 

posted @ 2020-05-08 14:54  thefatcat  阅读(234)  评论(0编辑  收藏  举报