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,对于没有被访问过的每个数,都有两种可能,即是否加入当前的子集合。