文章目录
- 1 理解题目
- 2 分析
- 2.1进一步优化
- 2.2 根据花花酱解答
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(kN−kk!)O(k^{N-k}k!)O(kN−kk!),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;}}