首先我们可以知道有两种情况一定是false的,一种是整个数组的总和不能被k整除,还有一种是数组中的最大值大于均分后的平均值。
其次我们先将数组排序,并且建立一个用来标记每个元素是否已经被使用的bool数组。
接下来就是最重要的回溯算法了。
设均分后的每组之和为sum。
我们以上图的数组为例,第一步先从最大且没被使用的数i开始, 求出i与sum的差值。如果它被使用,则一定能在接下来的数组中找到和为sum-i的元素集合(集合大小未知)。
如果不能找到和为sum-i的集合,就需要将i向前移动一位,重复这个过程直到找到一组符合的元素(true),或者遍历完了数组(false)。
显让这个过程是递归的,递归的边界是i已经小于0,或者当前i就等于当前的sum。
不过要注意的是这个递归也包涵了回溯的循环,需要找遍所有的i,以及它们后面的数组是否存在sum-i的集合,然后才能跳出边界。
class Solution {
public:
bool canPartitionKSubsets(vector<int>& nums, int k) {
bool* used=new bool[nums.size()];
fill(used,used+nums.size(),false);
sort(nums.begin(),nums.end());
int sum=0;
int i,j;
for(int x:nums)
sum+=x;
if(sum%k!=0)
return false;
sum=sum/k;
if(nums[nums.size()-1]>sum)
return false;
for(i=0;i<k;i++)
{
j=nums.size()-1;
while(j>=0&&used[j])
j--;
if(j<0||!dp(nums,used,sum-nums[j],j-1))
return false;
used[j]=true;
}
return true;
}
bool dp(vector<int>& nums,bool* used,int sum,int start)
{
if(sum==0)
return true;
int i=start;
while(i>=0)
{
while(i>=0&&(used[i]||nums[i]>sum))
i--;
if(i<0)
return false;
if(nums[i]==sum)
{
used[i]=true;
return true;
}
if(dp(nums,used,sum-nums[i],i-1))
break;
else
i--;
}
if(i<0)
return false;
used[i]=true;
return true;
}
};