leetcode
leetcode 601 ~ 650
划分为k个相等的子集

划分为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整除时,是否考虑了所有元素都为零的特殊情况?
是的,考虑了所有元素都为零的情况。在这种特殊情况下,数组总和为零,如果k不为零,则总和除以k的余数也为零,因此可以被k整除。由于所有元素都是0,可以很容易地将它们分配到k个子集中,每个子集的和也将为0,满足题目要求。
🦆
为什么在递归函数中,对j进行循环时要检查`if j and cur[j] == cur[j - 1]`,这里的逻辑是如何帮助减少重复计算的?
在递归函数中,检查`if j and cur[j] == cur[j - 1]`是为了避免重复的子集配置产生相同的搜索路径。当两个连续的子集目前的和相同时,在添加新元素后,这两个子集是可交换的,即它们会产生相同的子集分配但顺序不同,从而导致重复计算。通过跳过这种情况,我们可以有效减少搜索空间,避免不必要的计算,提高算法效率。
🦆
数组在进行降序排序后,是如何利用这个排序来进行剪枝优化的?具体的剪枝逻辑是什么?
数组进行降序排序后,可以帮助更快地达到子集和的上限。具体来说,因为较大的数先被处理,它们更快地填满接近目标和s的子集。如果一个较大的数无法放入任何当前的子集中(因为加入后会超过目标和s),那么较小的数也不可能放入,这样就可以立即中断当前的搜索路径。这种方法减少了不必要的递归调用,通过快速检测不可能的路径来提高效率。
🦆
在回溯法中,如果当前数加入任何子集后子集总和超过目标和s,会如何处理?这是否意味着当前分支直接失败?
是的,如果当前数加入任何子集后使得子集总和超过目标和s,则当前的分支会直接失败。在该情况下,算法会停止进一步探索这个分支并进行回溯,尝试将当前数加入其他子集或更改之前的分配。这是回溯法的一个关键特性,允许它通过尝试不同的组合来排除不可能满足条件的路径,从而找到可能的解决方案。

相关问题

分割等和子集

给你一个 只包含正整数 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

 

示例 1:

输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。

示例 2:

输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。

 

提示:

  • 1 <= nums.length <= 200
  • 1 <= nums[i] <= 100