1,题目要求
Given a non-empty array containing only positive integers, find if the array can be partitioned into two subsets such that the sum of elements in both subsets is equal.
Note:
- Each of the array element will not exceed 100.
- The array size will not exceed 200.
Example 1:
Input: [1, 5, 11, 5]
Output: true
Explanation: The array can be partitioned as [1, 5, 5] and [11].
Example 2:
Input: [1, 2, 3, 5]
Output: false
Explanation: The array cannot be partitioned into equal sum subsets.
给定仅包含正整数的非空数组,查找是否可以将数组划分为两个子集,使得两个子集中的元素总和相等。
2,题目思路
对于这道题,给定一个数组,判断该数组能否划分成两个子集,两个集合的和是相等的。
这道题,是之前的一道题的解法基础:
Target Sum
上面这道题,在底层用的就是子集部分和的概念,当时并没有对这个部分和的计算做详细讲解,而这道题就是对这个问题的详解。
首先,这道题的解法的理念基础是,对于一个数组nums
,假设我们存在一个集合P和集合N,它们的和分别为sum(P)
与sum(PN
。
假设集合P与N是满足条件的,那么我们有:
sum (P) + sum(N) = sum(nums)
sum( P ) = sum(N)
因此,我们有以下结论:
- 如果存在这样的子集,我们有
sum(P) = sum(nums) / 2
。
即,问题就转化为了,在一个集合中是否存在一个子集,它的和是整个集合的和的一半。
在方法上,我们可以使用动态规划的办法。其中,我们设置DP[j]
表示对于值j
,nums
是否存在一个子集,其和等于j
。
基本思想在于:
DP[i] = DP[i] || DP[i - num];
然而动态规划的方法运算速度较慢,这里,我们可以使用bitset
的方法实现动态规划的思想。
- 对于
bitset
,它存储的是二进制数位。 bitset
就像一个bool类型的数组一样,但是有空间优化:bitset
中的一个元素一半只占1bit,相当于一个char所占空间的八分之一。bitset
中的每一个元素都可以被单独访问,例如对于一个叫做foo的bitset
,foo[3]访问了它的第四个元素,这点类似于数组。bitset
有一个特性:整数类型和布尔数组都可以转化为bitset
。bitset
的大小在编译时就需要确定。
具体用法参考博客:
C++ bitset——高端压位卡常题必备STL
对于这道题,我们用bitset
来替代DP
,bits[j]
的含义与DP[j]
的含义相同。
我们使用左移操作和或操作来记录所有的可能的加和。
对于例子,nums = [2,3,5]
,初始情况下bits
设置为1,对nums进行遍历:
nums = 2,bits = 101
,表示nums的子集和可以是0和2;nums = 3,bits = 101101
, 表示nums的子集和可以是0、2、3、5;nums = 5,bits = 10110101101
,表示nums的子集和可以是0、2、3、5、7、8、10。
于是,我们只需要看bits[(2+3+5)/2]
是否为1即可。
另外,题目给出的是最大元素的值不超过100,元素个数不超过200,因此我们有sum的最大值为:
- 100*200 = 20000
因此,sum/2我们有:10000,又因为我们需要一位记录0,因此,bitset
的大小为10001。
两个利用位运算的简写:
- 判断是否是偶数:
sum & 1
,如果为1,说明是奇数,如果是0,说明是偶数; - 除以二:
sum >> 1
3,代码实现
1,动态规划
int x = []() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
return 0;
}();
class Solution {
public:
bool canPartition(vector<int>& nums) {
if(nums.size() == 0)
return true;
int sum = accumulate(nums.begin(), nums.end(), 0);
int target = sum/2;
if(sum%2 != 0)
return false;
vector<bool> DP(target+1, false);
DP[0] = true;
for(auto n : nums)
for(int i = target;i >= n;i--)
DP[i] = DP[i] || DP[i - n];
return DP[target];
}
};
2,利用bitset
实现动态规划思想
class Solution {
public:
bool canPartition(vector<int>& nums) {
bitset<10001> bits(1);
int sum = accumulate(nums.begin(), nums.end(), 0);
for (auto n : nums) bits |= bits << n;
return !(sum & 1) && bits[sum >> 1];
}
};