LeetCode破解之划分为k个相等的子集

116 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第20天,点击查看活动详情

题目描述

给定一个整数数组 nums 和一个正整数 k,找出是否有可能把这个数组分成 k 个非空子集,其总和都相等。

示例 1:

输入: nums = [4, 3, 2, 3, 5, 2, 1], k = 4

输出: True

说明: 有可能将其分成 4 个子集(5),(1,4),(2,3),(2,3)等于总和。

题目分析

先理一下这个题目,我们是希望在k个桶中放入总共n个数,使得各桶中的数字之和相等。所以这个题目涉及各种选择尝试,且不是可重用子问题相关,我们选择使用回溯法解答该题,使用track数组存储各桶中的数字之和,终止回溯的条件是传入的track状态数组各元素都等于目标值target,该target就是初始nums数组各元素之和除以k,因为nums各元素都要求是大于零的整数,所以target肯定也要是正整数。在选择&撤销选择环节我们就往track数组对应元素加上或者减掉nums[idx]的值。

回溯解法

class Solution {
    int[] visited;
    int sum;
    int target;
    public boolean canPartitionKSubsets(int[] nums, int k) {
        sum = IntStream.of(nums).sum();
        if(sum % k != 0) return false;
        int n = nums.length;
        visited = new int[n];
        Arrays.sort(nums);
        target = sum / k;
        return dfs(nums, k, 0, 0);
    }
    
    public boolean dfs(int[] nums, int k, int start, int cnt){
        if(k == 1){
            return true;
        }
        if(cnt == target){
            
            return dfs(nums, k - 1, 0, 0);
        }
        for(int i = start; i < nums.length; i++){
            if(visited[i] == 1) continue;
            if(cnt + nums[i] > target) break;
            visited[i] = 1;
            if(dfs(nums, k, i, cnt + nums[i])) return true;
            visited[i] = 0;
        }
        return false;
    }
}


暴力模拟

分析:暴力解法也可以行,主要思想跟大部分暴力方法相似,将第pos个数依次放到k个子集中的一个。剪枝2的思想就是:如果两个子集的和相同,那么当前第pos个数放到两个子集所得的结果也是一样的,所以可以直接跳过第二个子集。

注意:暴力虽香,但是耗时太长。看自己选择吧

class Solution {
    public boolean canPartitionKSubsets(int[] nums, int k) {
    if (k > nums.length) return false;
    int sum = 0;
    for (int v : nums) sum += v;
    if (sum % k != 0) return false;
    
    int used = 0; 
    int target = sum / k;
    return backtrack(k, 0, nums, 0, used, target);
}

HashMap<Integer, Boolean> memo = new HashMap<>();

boolean backtrack(int k, int bucket,
                  int[] nums, int start, int used, int target) {        
    if (k == 0) {
        return true;
    }
    if (bucket == target) {
        
        boolean res = backtrack(k - 1, 0, nums, 0, used, target);
        memo.put(used, res);
        return res;
    }
    
    if (memo.containsKey(used)) {
        return memo.get(used);
    }

    for (int i = start; i < nums.length; i++) {
        if (((used >> i) & 1) == 1) {
  
            continue;
        }
        if (nums[i] + bucket > target) {
            continue;
        }
        used |= 1 << i; 
        bucket += nums[i];
        if (backtrack(k, bucket, nums, i + 1, used, target)) {
            return true;
        }
        used ^= 1 << i; 
        bucket -= nums[i];
    }
    return false;
}
}