题目
给定一个整数数组 nums 和一个正整数 k,找出是否有可能把这个数组分成 k 个非空子集,其总和都相等。
示例 1:
输入: nums = [4, 3, 2, 3, 5, 2, 1], k = 4输出: True说明: 有可能将其分成 4 个子集(5),(1,4),(2,3),(2,3)等于总和。
示例 2:
输入: nums = [1,2,3,4], k = 3输出: false
提示:
1 <= k <= len(nums) <= 160 < nums[i] < 10000-
解题方法
递归回溯+剪枝优化
递归回溯的方式遍历每个元素的全部分配情况,并对分配情况进行剪枝优化执行效率。创建
k个子集,剪枝策略如下:- 第
1个元素只能够放入到第一个子集中,不用考虑放入到其他子集的情况。 - 如果当前元素加当前子集的和超过数组和的均值时,跳过当前子集。
- 如果当前子集与上一个子集和一致,则跳过当前子集。
- 第
时间复杂度O(k^n),空间复杂度O(n)
C++代码:
class Solution {private:int average;public:bool dfs(vector<int>& nums, vector<int>& buckets, int idx, int k) {if(idx==nums.size()) {for(int sum : buckets) {if(sum != average) return false;}return true;}bool result = false;for(int i=0; i<k; i++) {if(idx==0 && i>0) break;if(buckets[i] + nums[idx] > average) continue;if(i>0 && buckets[i] == buckets[i-1]) continue;buckets[i] += nums[idx];result = dfs(nums, buckets, idx+1, k);buckets[i] -= nums[idx];if(result) break;}return result;}bool canPartitionKSubsets(vector<int>& nums, int k) {int sum = 0;for(int num : nums) sum+=num;if(sum%k) return false;average = sum / k;vector<int> buckets(k, 0);return dfs(nums, buckets, 0, k);}};
动态规划+状态压缩
使用一个整数表示一个集合(二进制表示中每一位代表数组中对应位的数字是否包含在集合内部)。使用动态数组dp[i]描述集合i能否被分为k个相等的子集。
时间复杂度O(n*k^n),空间复杂度O(k^n)。
C++代码:
class Solution {
public:
bool canPartitionKSubsets(vector<int>& nums, int k) {
int len = nums.size();
if(len<k) return false;
int average = 0;
for(int num : nums) average += num;
if(average%k) return false;
average /= k;
int size = (1<<len);
vector<int> dp(size, -1);
dp[0] = 0;
for(int i=1; i<size; i++) {
for(int j=0; j<len; j++) {
if( !(i&(1<<j)) ) continue;
int pre = i & ~(1<<j);
if(dp[pre]!=-1 && dp[pre]+nums[j]<=average) {
dp[i] = (dp[pre]+nums[j])%average;
break;
}
}
}
return dp[size-1]==0;
}
};
