698. Partition to K Equal Sum Subsets

1 理解题目

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.
输入:一个int数组nums,一个int k
规则:将nums分成k个子数组,每个子数组的和相等
输出:true:如果可以将nums分成k个和相等的子数组。否则false。

Example 1:

Input: nums = [4, 3, 2, 3, 5, 2, 1], k = 4
Output: True
Explanation: It’s possible to divide it into 4 subsets (5), (1, 4), (2,3), (2,3) with equal sums.

2 分析

nums能分成k份,每一份的和应该是总和/k。那就首先确认:总和%k=0。
每一个子数组的和应该是:target=总和/k。如果某个元素的值>target,那也是不可分的。每个元素至少有一个元素,比target大的元素单独成一个子数组,不符合和为target的要求。

现在就该想怎么把这些元素分到k个子数组中。
最开始映入我脑中的是双指针。将nums排序,一个指针从左开始,一个指针从右开始。后来一想,子数组中的元素不一定是2个,被例题束缚了思维。那就不能这样做。但数组排序应该对。至于为什么,还不清楚。

那就尝试用枚举的方法。参考力扣官网
将第0个元素1,可以放在第0个子数组中、第1个子数组、第2个子数组、第3个子数组。
将第1个元素2,尝试放入第0,1,2,3个子数组,只要放入之后的和不超过target即可。

一直放到最后一个元素,将所有数字都放入了子数组中。

这里放入的过程不是直接将元素放进去,而是放入的是元素的和。

一个重要的细节是,通过判断 if (groups[i] == 0) break;这是因为在尝试了各种可能之后,groups[i]没有合适的选项,所以直接返回false;

class Solution {
    private int[] nums;
    public boolean canPartitionKSubsets(int[] nums, int k) {
        int sum = Arrays.stream(nums).sum();
        if(sum % k >0) return false;
        int target = sum/k;       
        Arrays.sort(nums);
        if(nums[nums.length-1]>target) return false;
        this.nums  = nums;
        
        int[] groups = new int[k];
        return dfs(groups,0,target);
    }
    private boolean dfs(int[] group, int index, int target){
        if(index>=nums.length) return true;
        int  v = nums[index++];
        for(int i=0;i<group.length;i++){
            if(group[i] + v<=target){
                group[i] += v;
                if(dfs(group,index,target)) return true;
                group[i] -= v;
            }
            if(group[i] == 0) break;
        }
        return false;
    }
}

2.1进一步优化

优化的第一个地方是将数组末尾直接等于target的删除。这个步骤优化效果不明显。
优化的第二个地方是遍历nums从最大值开始遍历。自己可以手写一下[1,2,2,3,3,4,5]这个例子,从大到小,与从小到大的枚举情况,可以发现从大到小,可以很快找到答案。

class Solution {
    private int[] nums;
    public boolean canPartitionKSubsets(int[] nums, int k) {
        int sum = Arrays.stream(nums).sum();
        if(sum % k >0) return false;
        int target = sum/k;       
        Arrays.sort(nums);
        if(nums[nums.length-1]>target) return false;
        this.nums  = nums;
        int index = nums.length-1;
        while(index>=0 && nums[index]==target){
            index--;
            k--;
        }
        int[] groups = new int[k];
        return dfs(groups,index,target);
    }
    private boolean dfs(int[] group, int index, int target){
        if(index<0) return true;
        int  v = nums[index--];
        for(int i=0;i<group.length;i++){
            if(group[i] + v<=target){
                group[i] += v;
                if(dfs(group,index,target)) return true;
                group[i] -= v;
            }
            if(group[i] == 0) break;
        }
        return false;
    }
}

时间复杂度 O ( k N − k k ! ) O(k^{N-k}k!) O(kNkk!),N是nums的长度。

2.2 根据花花酱解答

c++代码可以过,java代码超时。来源地址

class Solution {
    private int[] nums;
    public boolean canPartitionKSubsets(int[] nums, int k) {
        int sum = Arrays.stream(nums).sum();
        if(sum%k!=0) return false;
        int target = sum/k;
        if(nums[nums.length-1]>target) return false;
        
        Arrays.sort(nums);
        this.nums = nums;
        return dfs(0,0,k,target);
    }
    
    private boolean dfs(int current,int used,int k,int target){
        if(k==0) return (used == (1<<nums.length)-1);
        
        for(int i=0;i<nums.length;i++){
            if((used & (1<<i))>0) continue;
            int t = current + nums[i];
            if(t>target) break;
            int newUsed = (used | (1<<i));
            if(t==target){
                if(dfs(0,newUsed,k-1,target)) return true;
            }else{
                if(dfs(t,newUsed,k,target)) return true;
            }
        }
        return false;
    }
  
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值