划分为k个相等的子集
难度:
标签:
题目描述
代码结果
运行时间: 51 ms, 内存: 16.0 MB
/*
题目思路:
1. 计算数组的总和并检查是否能被k整除。
2. 使用目标和计算每个子集的目标和。
3. 使用Stream对数组排序并进行反转。
4. 通过递归函数尝试将每个数字分配到k个子集中。
5. 使用辅助数组来跟踪每个子集的当前和。
*/
import java.util.Arrays;
import java.util.Collections;
import java.util.stream.Collectors;
public class Solution {
public boolean canPartitionKSubsets(int[] nums, int k) {
int sum = Arrays.stream(nums).sum();
if (sum % k != 0) return false;
int target = sum / k;
nums = Arrays.stream(nums).boxed().sorted(Collections.reverseOrder()).mapToInt(Integer::intValue).toArray();
int[] bucket = new int[k];
return canPartition(nums, bucket, target, 0);
}
private boolean canPartition(int[] nums, int[] bucket, int target, int index) {
if (index == nums.length) return true;
for (int i = 0; i < bucket.length; i++) {
if (bucket[i] + nums[index] > target) continue;
bucket[i] += nums[index];
if (canPartition(nums, bucket, target, index + 1)) return true;
bucket[i] -= nums[index];
if (bucket[i] == 0) break;
}
return false;
}
}
解释
方法:
这个题解使用了回溯算法和剪枝优化。首先计算数组元素总和是否能被k整除,如果不能则直接返回False。然后将数组按从大到小排序,便于剪枝。接着使用递归回溯,维护一个大小为k的数组cur,表示当前k个子集的和。对于每个数,依次尝试放入每个子集中,如果放入后子集和不超过目标和s,则递归到下一个数。如果递归到最后一个数,说明找到了一种合法方案,返回True。如果尝试了所有可能性都无法找到合法方案,则返回False。在递归过程中,如果当前子集和与前一个子集和相等,则可以跳过,因为这种情况已经被之前的分支考虑过了,这样可以去除重复计算,减少时间复杂度。
时间复杂度:
O(k^n)
空间复杂度:
O(n)
代码细节讲解
🦆
在计算数组元素总和是否能被k整除时,是否考虑了所有元素都为零的特殊情况?
▷🦆
为什么在递归函数中,对j进行循环时要检查`if j and cur[j] == cur[j - 1]`,这里的逻辑是如何帮助减少重复计算的?
▷🦆
数组在进行降序排序后,是如何利用这个排序来进行剪枝优化的?具体的剪枝逻辑是什么?
▷🦆
在回溯法中,如果当前数加入任何子集后子集总和超过目标和s,会如何处理?这是否意味着当前分支直接失败?
▷