题目链接:https://leetcode-cn.com/problems/partition-equal-subset-sum/
给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
注意:
示例 1:
输入: [1, 5, 11, 5]
输出: true
解释: 数组可以分割成 [1, 5, 5] 和 [11].
示例 2:
输入: [1, 2, 3, 5]
输出: false
解释: 数组不能分割成两个元素和相等的子集.
首先我们排除不能分割成等和子集的情况:
(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();
}
};
复杂度分析
时间复杂度: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;
}
};
建立一个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;
}
};