【leetcode】416 分割等和子集(动态规划)

题目链接:https://leetcode-cn.com/problems/partition-equal-subset-sum/

题目描述

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

注意:

  1. 每个数组中的元素不会超过 100
  2. 数组的大小不会超过 200

示例 1:

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

输出: true

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

示例 2:

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

输出: false

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

思路

1 动态规划-二维数组

首先我们排除不能分割成等和子集的情况:
(1)首先我们将数组元素求和得到S,如果S为奇数,则肯定不能划分为等和子集。
(2)设 sum = S/2,则我们的问题转化为这n个数字组成数组能否得到和为sum
这是一个典型的0,1背包问题,就是每个数字选与不选。
其实我们可以从题目的注意中也能想到,因为题目限定了元素数<=100,而数组大小<=200;而开辟数组空间最多只有100万,刚好 = 100 * (100 * 200/2)

于是我们设类型为bool的二维数组dp,dp[i][j]表示前i个数字能否得到和为j。动态方程为:

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

其中dp[i][nums[i]] = true
注意:找到可行解后应该直接返回,避免后续无效计算。

复杂度分析
时间复杂度:O(nm)
空间复杂度:O(nm)
m为数组和的一半

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        if(nums.size()<=1) return false;
        int sum = accumulate(nums.begin(),nums.end(),0); 
        if (sum & 0x01) return false;   // 如果数组和为奇数,肯定无法分割
        sum = sum >> 1;
        vector<vector<bool>> dp(nums.size(), vector<bool>(sum+1, false));
        for (int i = 0; i < nums.size(); ++i) {
            if(nums[i] == sum) return true; // 只选该数字
            dp[i][nums[i]] = true;          // 只选该数字
            if(i==0) continue;
            for (int j = 1; j < sum+1; ++j) {
                bool choose = false;
                bool notChoose = dp[i-1][j];        // 不选该数字
                if (j-nums[i]>0)
                    choose = dp[i-1][j-nums[i]];    // 选该数字
                else if(j == nums[i])
                    choose = true;

                dp[i][j] = dp[i][j] || choose || notChoose;
            }
            if(dp[i][sum]) return true; // 如果找到,直接返回
        }
        return dp.back().back();
    }
};

【leetcode】416 分割等和子集(动态规划)_第1张图片
上面二维数字可以进行进一步优化:

动态规划-一维数组

复杂度分析
时间复杂度:O(nm)
空间复杂度:O(m)
m为数组和的一半

//当成背包问题解决
class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = accumulate(nums.begin(), nums.end(), 0);
        if (sum % 2) return false;
        vector<int> dp(sum / 2 + 1);
        dp[0] = 1;
        for (int i = 0; i < nums.size(); ++i) {
            for (int j = sum / 2; j >= nums[i]; --j) {
                dp[j] |= dp[j - nums[i]];
            }
            if (dp[sum / 2]) return true;
        }
        return false;
    }
};

【leetcode】416 分割等和子集(动态规划)_第2张图片

动态规划-bitset位运算

建立一个bitset,第i位如果是1表示当前数组可以表示和为i

bs |= (bs << num)

这一步同时做了两件事:
(1)保留位为1的比特位,即不取num
(2)将取num之后可以表示的位置为1
感叹位运算的强大,直接并行完成了,将时间复杂度从O(nm)直接降为O(n)!

复杂度分析
时间复杂度:O(n)
空间复杂度:O(m)

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = accumulate(nums.begin(), nums.end(), 0);
        if (sum % 2) return false;
        bitset<10001> bs(1);
        for (auto num : nums) {
            bs |= (bs << num);
            if (bs[sum / 2])
                return true;
        }
        return false; 
    }
};

【leetcode】416 分割等和子集(动态规划)_第3张图片

你可能感兴趣的:(LeetCode)