题目:给定一个数组,让你将其划分为k个的非空子集合,使得每一个子集的和都相等。
例如:输入: nums = [4, 3, 2, 3, 5, 2, 1], k = 4
输出: True
说明: 有可能将其分成 4 个子集(5),(1,4),(2,3),(2,3)等于总和。
思路分析:对于相关集合,排列,组合,子集,切割,棋盘类问题,我们都应该下意识的想到回溯法解题。而关于回溯法三步骤如下:
void Test(){
if(符合条件,如本题就是划分成k个相等子数组){
return ;
}
for(){
将元素放入目标集合;
向下递归调用Test()方法;
将元素从目标集合中删去,进行回溯;
}
}
根据上面模板,我们可以分析:
1、我们的递归函数要什么参数?数组肯定要,要分成多少组肯定要,还有一个就是判断我们什么时候return的标志位cur;cur就是数组nums的下标。当这个数组走完一遍时,就说明该结束了。
2、什么时候return?就是我们的cur为-1时,因为我们cur是从最后一个元素开始向前扫的。所以当扫到最前面一个元素后就应该结束了。至于为什么要从最后一个开始扫,是因为我们为了节约运行时间,会将数组从小到大排序,我们从大到小选取数组,如果该比较大的数组元素都能比k份中每一份大,是不是就说明这个数组必不能分成k等份,因此比每一份大的这个元素就不能切分成k。
3、for内部怎么写?我们要将数组划分为k份,就先声明k个数组,每一个数组大小就是nums数组累加和除以k。遍历循环k个数组,将每一个元素往k个数组内试着放入。如果我们这第i个数组不够当前元素放,那就直接跳过,试下一个数组。如果正好放下或者放下后剩余容量还能放下最小元素,就说明可以往这个数组里放。下面就是递归调用,前两个参数不变,后面参数变成-1。最后将这个元素从这个数组中取出即可。
class Solution {
int[] bucket;
public boolean canPartitionKSubsets(int[] nums, int k) {
if (k == 1) return true; //如果k是1,直接返回true。
int len = nums.length;
int sum = 0;
for(int i=0; i < nums.length; i++){
sum = sum + nums[i]; // 算出nums的总和。
}
if (sum % k != 0)
return false; //子集分不出k份,直接false。
sum /= k;// sum变为每个子集的和。
Arrays.sort(nums);
/*
为什么要排序呢,其实不排序这道题也能做对,但是由于时间的关系就t了。
排序就是为了优化时间,怎么优化呢?
我们从nums中最大的数开始找,如果最大的数比子集和都要大,或者装下它后没到子集和的大小但是装不下nums中最小的值了,
那么这个nums绝对是false,因为有一个这么大的数在nums里,你把它放在哪个子集里都不合适。
*/
bucket = new int[k];//这个数组里放的是子集和,总共有k个。相当于k个桶,元素一个一个往里面放。
Arrays.fill(bucket, sum);
return dfs(k, nums, len - 1);
}
public boolean dfs(int k, int[] nums, int cur) {//cur为当前的位置,从最后开始往前走。
if (cur < 0)
return true;// cur走到-1时,说明所有的数全部都放进桶里了。这时一定是true
for (int i = 0; i < k; i++) {
//两种可能,这个数正好是桶当前的容量,或者将这个数放进桶后这个桶还能再放别的数。
if (bucket[i] == nums[cur] || bucket[i] - nums[cur] >= nums[0]) {
//将cur放进第一个桶里,如果不行,拿出来再放进第二个桶里。
//区别就是如果cur放进第一个桶,那么下一个数如果符合也会放在第一个桶,可是最后发现是false,那么可能我应该把这两个数分开来放。
bucket[i] -= nums[cur];
if (dfs(k, nums, cur - 1))
return true;
bucket[i] += nums[cur];
}
}
return false;
}
//这里讨论一种问题,为什么只要判断cur<0就能说明true,而不需要判断一下bucket数组中的值是否全部都是0。
/*
有没有可能bucket数组中的数有剩余但是cur已经小于0了呢?答案是不可能。
因为如果cur<0,那么说明nums中的所有数全部都放进去了,如果全部都放进去,bucket中的数尽管可能不全为0,但是数组中的和一定是0。
因为bucket的和与nums的和是一样的,所以我用nums中的数在不重复的状态下去减bucket中的数,全部减去的情况下bucket中的和一定是0。
可是真的可能有负数吗,第26行我们的if条件严格判断如果没有一个桶内剩余的空间能放下这个数的话是不会执行递归的,只会返回一个false,
因此cur一旦有一个递归没下去cur就不可能为-1,只能换其他的情况再去试。
*/
}