416.分割等和子集
给你一个 只包含正整数 的 非空 数组 nums
。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1:
输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:
输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。
提示:
1 <= nums.length <= 200
1 <= nums[i] <= 100
错误(或者叫不完全正确)思路
一种我首先想到的思路是使用回溯法找到整个集合的所有子集,然后判断是否符合条件,但其实回溯的时间开销非常高,找子集能够达到惊人的
O
(
2
n
)
O(2^n)
O(2n)级别,因此回溯的手段一般仅限于小样本数据(n为二十多一点已经是时间限制内回溯问题的极限了),所以本题不能采用回溯的手段,如果本题的样本量是n<20
,那么可使用以下代码:
//对本题而言的错误代码!!!!
class Solution {
private:
bool ans = false;
void traceBack(vector<int> &nums,int curIdx,int curSum,int sum){
if(curIdx==nums.size()||ans==true||curSum>sum){
return;
}
if(curSum == sum){
ans = true;
return;
}
traceBack(nums,curIdx+1,curSum,sum);
traceBack(nums,curIdx+1,curSum+nums[curIdx],sum-nums[curIdx]);
}
public:
bool canPartition(vector<int>& nums) {
int sum=0;
sort(nums.begin(),nums.end());
for(int i=0;i<nums.size();++i){
sum+=nums[i];
}
traceBack(nums,0,0,sum);
return ans;
}
};
正确思路
看到这种样本量对回溯手段而言比较大但是本身又不算大的问题,应该考虑往动态规划上面靠,本题就是一个0-1背包的变式,我们可以想到开一个二维的布尔型dp数组,对于dp[i][j]
而言,则是代表在[0,i]
区间上,是否存在一系列元素,使得元素之和为j
的意思,之后我们考虑dp的状态转换,对于每一列,则是背包大小为i
的情况进行元素分配,而对于每一行则是对于相同的取值区间调整背包大小。对于每个数组元素则是进行是否选取的决策,但是在决策之前,按照0-1背包的思路,应该先判断是否装满了背包,也就是对于每个nums[i]
,判断nums[i]
与j
之间的大小关系,如果nums[i]<j
那么说明没有装满,可以继续装(当然这里是可装可不装),如果nums[i]>j
说明满了,就不能装了。而且我们发现这个过程实际上是有前缀性的,换句话说,对于dp数组的同一列,如果这一列的某一行为true
,那么该列的这一行以下的所有行都为true
(这是很显而易见的,因为背包大了,区间不变)
另外拓展两个api
accumulate(nums.begin(),nums.end(),0)
:功能是求nums
数组的和,前两个参数分别是数组第一个元素的迭代器和最后一个元素的下一个迭代器,第三个参数则是初项的大小max_element(nums.begin(),nums.end())
:功能是找到数组的最大元素,返回的是最大元素的迭代器,所以在代码中使用时进行了解引用
代码
class Solution {
public:
bool canPartition(vector<int>& nums) {
int n = nums.size();
if(n==1){
return false;
}
int sum = accumulate(nums.begin(),nums.end(),0);
if(sum&1){
return false;
}
int maxNum = *max_element(nums.begin(),nums.end());
int target = sum/2;
if(maxNum>target){
return false;
}
vector<vector<bool>> dp(n,vector<bool>(target+1,false));
for(int i=0;i<n;++i){
dp[i][0] = true;
}
dp[0][nums[0]] = true;
for(int i=1;i<n;++i){
int curNum = nums[i];
for(int j=1;j<=target;++j){
if(j==target&&dp[i][j]) return true;
if(curNum<j){
dp[i][j] = dp[i-1][j] | dp[i-1][j-curNum];
}else{
dp[i][j] = dp[i-1][j];
}
}
}
return dp[n-1][target];
}
};