LeetCode 698 Partition to K Equal Sum Subsets

Problem Description:  Given an array of integers nums and a positive integer k, find whether it's possible to divide this array into k non-empty subsets whose sums are all equal.

输入是一个整型数组和一个正整数k,判断是否能将正整数组中的元素分组到k个非空子集合并保持每个子集合中元素的和相等。

Note:

  • 1 <= k <= len(nums) <= 16.
  • 0 < nums[i] < 10000.

解题思路:

此题看似跟416. Partition Equal Subset Sum接近,但并不能用类似的思路来解题。从数据的规模来看,采用深度优先搜索来暴力搜索是可行的。

看了hint后想到的是创建一个大小为k的int[] subsets数组,然后顺序考虑每一个nums中元素,对于每一个元素考虑将其加到subsets中的每一个子集合中。对于nums中每一个数,均有k种选择,此种解法的时间复杂度为O(k^n)。

此种算法可进行一些优化(条件见代码),代码如下

class Solution {
    public boolean canPartitionKSubsets(int[] nums, int k) {
        int sum = 0;
        for(int num : nums) sum += num;
        if(sum % k != 0) return false;
        int S = sum / k;
        int[] subsets = new int[k];
        return dfs(nums, subsets, 0, S);
    }
    public boolean dfs(int[] nums, int[] subsets, int index, int tgt) {
        if(index == nums.length) {
            for(int i = 0; i < subsets.length; i++) {
                if(subsets[i] != tgt) return false;
            }
            return true;
        }
        boolean res = false;
        Set<Integer> set = new HashSet<>();
        for(int i = 0; i < subsets.length; i++) {
            if(tgt - subsets[i] >= nums[index] && set.add(subsets[i])) {  //2个减枝条件,1:某个集合加入nums[i]后大于tgt。2:nums相同的元素仅考虑一次
                subsets[i] += nums[index];
                res |= dfs(nums, subsets, index + 1, tgt);
                subsets[i] -= nums[index];
            }
        }
        return res;
    }
}

提交后顺利ac,运行时间858ms。若不优化,则TLE。

看了一下discuss caihao0727mail 的解答,总结一下思路:该博主做题的时候没有加nums元素为正数的条件,所以增加了cur_num,针对sum为0的情况,对于更新后的题目可以省去。

nums中的元素分成k份,只有一种分法(元素值相同即认为相同)-> 每次找到一组子集和为target就开始找下一个子集,直到k为1。

博主的解答如下:

public boolean canPartitionKSubsets(int[] nums, int k) {
        int sum = 0;
        for(int num:nums)sum += num;
        if(k <= 0 || sum%k != 0)return false;
        int[] visited = new int[nums.length];
        return canPartition(nums, visited, 0, k, 0, 0, sum/k);
    }
    
    public boolean canPartition(int[] nums, int[] visited, int start_index, int k, int cur_sum, int cur_num, int target){
        if(k==1)return true;
        if(cur_sum == target && cur_num>0)return canPartition(nums, visited, 0, k-1, 0, 0, target);
        for(int i = start_index; i<nums.length; i++){
            if(visited[i] == 0){
                visited[i] = 1;
                if(canPartition(nums, visited, i+1, k, cur_sum + nums[i], cur_num++, target))return true;
                visited[i] = 0;
            }
        }
        return false;
    }

start_index 是用来优化时间复杂度的,避免重复考虑。运行时间为7ms

我稍微修改一下后,运行时间为4ms,代码如下

class Solution {
    public boolean canPartitionKSubsets(int[] nums, int k) {
        int sum = 0;
        for(int num : nums) sum += num;
        if(sum % k != 0) return false;
        int tgt = sum / k;
        boolean[] visited = new boolean[nums.length];
        return dfs(nums, visited, 0, k, 0, tgt);
    }
    public boolean dfs(int[] nums, boolean[] visited, int start, int k, int sum, int tgt) {
        if(k == 1) return true;
        if(sum == tgt) return dfs(nums, visited, 0, k - 1, 0, tgt);
        for(int i = start; i < nums.length; i++) {
            if(!visited[i] && tgt - sum >= nums[i]) {
                visited[i] = true;
                if(dfs(nums, visited, i + 1, k, sum + nums[i], tgt)) return true;
                visited[i] = false;
            }
        }
        return false;
    }
}

时间复杂度应该为O(k*2^n)。对于个k个子集合,每个子集合的和为target,对于没有被访问过的每个数,都有两种可能,即是否加入当前的子集合。 

posted @ 2019-03-07 18:17  起点菜鸟  阅读(345)  评论(0编辑  收藏  举报