【DFS】LeetCode 698. 划分为k个相等的子集

题目链接

698. 划分为k个相等的子集

思路

这道题的核心在于:将数组划分为k个组,数组的每个元素只有可能属于一个组合. 以题目给的nums = [4, 3, 2, 3, 5, 2, 1], k = 4为例,此时target=5. 划分为4个组就相当于四个组合【】,【】,【】,【】.初始状态下均是空. 先找第一个和为target的组合——先将数组的第一个元素4添加进组合,【4】.此时当前组合的和小于target. 然后添加下一个元素3,【4,3】,我们发现3+4的和超过了target值5,添加的元素不符合我们的需求.那我们就把这个元素拿出去,变回【4】 继续添加下一个元素2,变成【4,2】,同样超过了目标值5。不满足,回溯——其实就是返回上一个状态【4】 再继续添加下一个元素3,【4,3】——和为7又超过了5.回溯变回【4】 以此类推到数组的最后一个元素1,把1添加进组合【4,1】,此时组合的和刚好等于目标值5。至此,我们拿到了第一个和为5的组合。 在这里说下,如果数组的最后一个元素不是1,而是2;回溯回到了【4】这个状态,我们发现数组中没有别的元素可以与4构成和为5的组合。那么继续回溯,回到【】这个状态,继续添加下一个元素3,变成【3】。 因为每个元素只能属于一个组合当中,所以我们需要一个数组used[]来记录哪些元素使用过,哪些没使用过。 我们回到这里,拿到了第一个和为5的组合【4,1】,数组中索引为0和索引为n-1的元素我们使用过了. 继续找第二个和为5的组合——因为数组中的第一个元素我们已经使用过了,所以从第二个元素开始添加,把第二个元素3添加进我们的组合【】中,变成【3】。此时和为3<目标值5,继续添加下一个元素2(注意:这里是下一个,不是第一个,因为再从头开始添加就会出现重复的组合).添加进元素2得到【3,2】,组合的和刚好为5。这时我们又找到了一个和为5的组合。索引为1和2的元素被我们使用了。大概思路就是这样,后面不再赘述了……

  1. 当我们在数组中找到k-1个满足题意的组合后,就可以return true了。因为剩下未选的元素的和肯定为数组总和sum-(k-1)*target=target。
  2. 当组合中的元素之和超过了目标值时,就没必要再往组合中添加元素了,直接回溯。
  3. 如果某个元素无法使得组合达到目标值,那么跟它值相同的元素也不需要添加了。举个例子,数组[1,3,3,3],k=2.可以计算出target=5.【1】——此时组合的和为1,小于目标值5,继续往组合中添加元素。添加第一个3,【1,3】,小于5,继续添加。我们发现随后继续添加两个3都超过了5,回溯到【1,3】这个状态。数组遍历完,依然找不到和为5的组合,继续回溯——【1】。此时,我们发现下一个要添加的元素还是3,但在之前我们已经试过了3无法使得当前组合达到目标值,所以就没必要再添加3了,直接continue.
  4. 从大到小遍历,这有点类似贪心的思想。比如,我们为了凑一定数量的钱,我们肯定先选择面额比较大的,最后再选面额小的。这样凑出来更快。再举个例子——组合【4】要达到5只需要找值为1的元素,而【1】要达到5则要把遇到的2,3,4都试一遍。

作者:Twinkle^^
链接:https://leetcode.cn/problems/partition-to-k-equal-sum-subsets/solutions/766599/javahui-su-jian-zhi-shou-ba-shou-jiao-hu-0equ/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

代码

class Solution {
    public boolean canPartitionKSubsets(int[] nums, int k) {
        int sum = 0;
        boolean[] used = new boolean[nums.length];
        Arrays.sort(nums);
        for(int i = 0; i < nums.length; i++){
            sum += nums[i];
        }
        if(sum % k != 0){
            return false;
        }
        
        int target = sum / k;
        if(nums[nums.length - 1] > target){
            return false;
        }
        
        return dfs(nums, nums.length - 1, target, 0, k, used);
    }

    public static boolean dfs(int[] nums, int begin, int target, int curSum, int k, boolean[] used) {
        //剪枝1
        if(k == 1){
            return true;
        }
        if(curSum == target){
            return dfs(nums, nums.length - 1, target, 0, k - 1, used);//找到了一个组合,还有k-1个.
        }
        //剪枝4
        for(int i = begin; i >= 0; i--){
            //使用过的元素就不能再使用了
            if(used[i]){
                continue;
            }
            //剪枝2
            if(curSum + nums[i] > target){
                continue;
            }
            used[i] = true;//添加元素nums[i]
            if(dfs(nums, i - 1, target, curSum + nums[i], k, used)){
                return true;//如果添加这个元素后,完成了题目要求,就return true.
            }
            used[i] = false;//回溯
            //剪枝3
            while(i > 0 && nums[i - 1] == nums[i]){
                i--;
            }
        }

        return false;
    }
}
posted @ 2023-03-07 10:07  Frodo1124  阅读(90)  评论(0编辑  收藏  举报