Leetcode——划分为k个相等的子集(目标和)

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

Leetcode——划分为k个相等的子集(目标和)_第1张图片

(1)回溯,剪枝,贪心

  • 将数组划分为k个组,使得每个组的和均相等。
  • 求出数组总和sum,如果sum除以k不为整数,肯定就不满足每个组的和相等,直接return false.
  • 拿到sum/k的值target后,target就代表每个组的和需要达到的值(超过或不足都不行)
  • 同时,如果数组的最大值>target也无法满足题意,return false.

剪枝:

  • 找到k-1个满足题意的组合后,就可以return true了。因为剩下未选的元素的和肯定为数组总和sum-(k-1)*target=target。
  • 当组合中的元素之和超过了目标值时,就没必要再往组合中添加元素了,直接回溯。
  • 如果某个元素无法使得组合达到目标值,那么跟它值相同的元素也不需要添加了
    • 举个例子,数组[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.
  • 从大到小遍历,这有点类似贪心的思想
class Solution {
    public boolean canPartitionKSubsets(int[] nums, int k) {
        int sum=0;
        int len = nums.length;
        boolean[] used = new boolean[len];
        Arrays.sort(nums);

        //求数组总和
        for (int i = 0; i < nums.length; i++) {
            sum += nums[i];
        }

        //如果sum除以k不为整数,肯定就不满足每个组的和相等,直接return false.
        //不能整除则子集分不出k份,直接false。
        if (sum % k != 0)
            return false;

        //拿到sum/k的值target后,target就代表每个组的和需要达到的值(超过或不足都不行)
        int target = sum / k;

        //如果数组的最大值>target也无法满足题意,return false.
        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;

            //添加元素nums[i]
            used[i]=true;

            //如果添加这个元素后,完成了题目要求,就return true.
            if (dfs(nums,i-1,target,curSum+nums[i],k,used))
                return true;

            //回溯
            used[i]=false;

            //剪枝3
            while (i > 0 && nums[i-1] == nums[i])
                i--;
        }
        return false;
    }
}

(2)桶

先算出子集的和是多少,并抽象成k个桶,每个桶的值是子集的和。然后尝试所有不同的组合(即放数到桶中),如果存在一种组合可以使每个桶都正好放下,那么返回可以。如果不存在,返回不可以。

class Solution {
    public boolean canPartitionKSubsets(int[] nums, int k) {
        //因为题目限制条件不用担心溢出
        int sum = 0;
        for(int i = 0; i < nums.length; i++){
            sum += nums[i];
        }
        if(sum % k != 0){
            return false;
        }
        //求出子集的和
        sum = sum / k;
        //排序 小的放最前面大的放最后面
        Arrays.sort(nums);
        //如果子集的和小于数组最大的直接返回false
        if(nums[nums.length - 1] > sum){
            return false;
        }
        //建立一个长度为k的桶
        int[] arr = new int[k];
        //桶的每一个值都是子集的和
        Arrays.fill(arr, sum);
        //从数组最后一个数开始进行递归
        return help(nums, nums.length - 1, arr, k);
    }
    
    boolean help(int[] nums, int cur, int[] arr, int k){
        //已经遍历到了-1说明前面的所有数都正好可以放入桶里,那所有桶的值此时都为0,说明找到了结果,返回true
        if(cur < 0){
            return true;
        }
        //遍历k个桶
        for(int i = 0; i < k; i++){
            //如果正好能放下当前的数或者放下当前的数后,还有机会继续放前面的数(剪枝)
            if(arr[i] == nums[cur] || (cur > 0 && arr[i] - nums[cur] >= nums[0])){
                //放当前的数到桶i里
                arr[i] -= nums[cur];
                //开始放下一个数
                if(help(nums, cur - 1, arr, k)){
                    return true;
                }
                //这个数不该放在桶i中
                //从桶中拿回当前的数
                arr[i] += nums[cur];
            }
        }
        return false;
    }
}

你可能感兴趣的:(LeetCode,leetcode,算法,java)