题目:
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.
大概意思就是给出一系列数,问能不能把这些数分成两组,两组的和相等。
题目分析:
显然,这些数的和是可以求得的,所以每组之和也是知道的,当和为奇数时显然不成立。当和为偶数时,每组之和是总和的一半,问题转化为是否可以从这些数中找到一些数的组合,和刚好是这个和的一半。
这个问题和背包问题有点类似,解题思路也可以和背包问题类似,使用动态规划。用一个二维数组dp,dp[i][j]若为1表示第i个数之前,有和为j的组合。当读到第i个数时,若nums[i]>sum/2,则d[i][j] = d[i-1][j],否则d[i][j]=d[i-1][j] || d[i-1][j-nums[i]],所以最后的结果就是dp[nums.size][sum/2]。类比背包问题,这个题也可以用一维数组就可以实现,dp[i]若为1则表示存在和为i的组合,具体实现如下。算法的时间复杂度是O(MXN),M是sum/2,N是数字的数量。
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum = accumulate(nums.begin(), nums.end(), 0);
if(sum&1) return false;
vector<int> dp(sum/2+1, 0);
dp[0] = 1;
for(int i = 0; i < nums.size(); i++)
{
for(int j = sum/2; j >= nums[i]; j--){
dp[j] = dp[j] || dp[j-nums[i]];
if(dp[sum/2]) return true;
}
}
return dp[sum/2];
}
};
本题除了使用动态规划解以外,也可以直接用递归算法来解,具体时间如下
class Solution {
public:
bool getSum(vector<int>& nums, int sum, int start)
{
for(int i = start; i >= 0; --i)
{
if(nums[i] == sum)
return true;
if(nums[i] > sum)
continue;
if(getSum(nums,sum-nums[i],i-1))
return true;
}
return false;
}
bool canPartition(vector<int>& nums) {
int sum = 0;
int n = nums.size();
if(!n)
return true;
for(int i = 0; i < n; ++i)
sum += nums[i];
if(sum%2)
return false;
sort(nums.begin(),nums.end());
return getSum(nums,sum/2,n-1);
}
};
有趣的是,本题使用递归算法,耗时远远低于使用DP算法,说明DP算法有时候效率也不高