698. 划分为k个相等的子集
给定一个整数数组 nums 和一个正整数 k,找出是否有可能把这个数组分成 k 个非空子集,其总和都相等。
示例 1:
输入: nums = [4, 3, 2, 3, 5, 2, 1], k = 4
输出: True
说明: 有可能将其分成 4 个子集(5),(1,4),(2,3),(2,3)等于总和。
示例 2:
输入: nums = [1,2,3,4], k = 3
输出: false
提示:
1 <= k <= len(nums) <= 16
0 < nums[i] < 10000
每个元素的频率在 [1,4] 范围内
思路:这是一道很有价值的回溯剪枝问题。一种做法是遍历数组内所有元素,得到划分的目标值,将元素依次放入k个子集,使得每个子集元素和等于目标值,整个过程就是遍历到当前元素,看具体放入哪个子集,如果没有溢出就继续往下,否则就回溯,直到遍历完最后的元素,也就是所有元素都已经放到子集里面了,就返回true,如果哪个子集都没法放就返回false,但这个过程会超时,需要优化,优化的点就是先对数组做个降序,优先让大的值先选择子集,还有就是剪枝,一旦发现放当前元素到子集i和上一个子集i-1的结果一样就直接剪枝
class Solution {
public boolean canPartitionKSubsets(int[] nums, int k) {
//降序排列, 优先让大的值选择去哪个子集
Arrays.sort(nums);
int left = 0, right= nums.length - 1;
while (left < right) {
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
left++;
right--;
}
int sum = 0;
for(int i = 0; i < nums.length; i++){
sum += nums[i];
}
if(sum % k != 0)
return false;
int target = sum / k;
int[] bucket = new int[k];
return backtrack(nums, 0, bucket, k, target);
}
public boolean backtrack(int[] nums, int index, int[] bucket, int k, int target){
// 结束条件:已经处理完所有球
if(index == nums.length){
return true;
}
for(int i = 0; i < k; i++){
// 如果某个元素进入当前子集导致元素和超过目标,就选择下一个子集
if(bucket[i] + nums[index] > target){
continue;
}
//如果当前子集和上一个子集内的元素和相等,则跳过,因为这样结果和上一个子集相等了
if(i > 0 && bucket[i] == bucket[i - 1])
continue;
// 放入第i个子集
bucket[i] += nums[index];
//处理下一个值
if(backtrack(nums, index + 1, bucket, k, target))
return true;
// 回溯
bucket[i] -= nums[index];
}
//所有子集都放不了
return false;
}
}
参考:LFool大神的笔记