LeetCode/划分k个相等的子集
给定一个整数数组 nums 和一个正整数 k,找出是否有可能把这个数组分成 k 个非空子集,其总和都相等
一. 回溯法
1. 对每个数,回溯放入子集(球视角)
只关心每个球是否成功放入,球在k个容器中做选择,做出选择后进入下一个状态
class Solution {
public:
bool canPartitionKSubsets(vector<int>& nums, int k) {
int sum = accumulate(nums.begin(), nums.end(), 0);
if (sum % k) return false;
sum /= k;
int n = nums.size();
vector<int> cur(k);
sort(nums.begin(), nums.end(), greater<int>());
return backtrack(nums,cur,0,sum);
}
//回溯为真,表示下标i及以后的球成功放入容器
bool backtrack(vector<int>&nums,vector<int>&cur,int i,int target){
if (i == nums.size()) return true;//边界条件,表示所有球放完
for (int j = 0; j < cur.size(); ++j) {//遍历所有容器
if (j && cur[j] == cur[j - 1]) //剪枝,跳过等价容器
continue;
cur[j] += nums[i];//选择把该球放入当前容器
if (cur[j] <= target && backtrack(nums,cur,i+1,target))//递归判断
return true;
cur[j] -= nums[i];//撤销选择
}
return false;
}
};
2. 对每个子集,回溯放入数(桶视角)
关心每一个容器是否装满,装满后递归判断下一个容器,每个容器从数组中遍历选数,并记录已经选过的数
这里用位图记录选过的数,同时用作记忆化搜索,辅助剪枝
class Solution {
public:
unordered_map<int,bool> m;//位图记录所有状态
bool canPartitionKSubsets(vector<int>& nums, int k) {
int sum = accumulate(nums.begin(),nums.end(),0);
if(sum%k) return 0;
sum/=k;
int state = 0;
vector<int> cur(k+1);
return backtrack(nums,0,cur,k,sum,state);//前k个桶能用start起始的数装满
}
bool backtrack(vector<int>&nums,int start,vector<int>&cur,int k,int target,int state){
if(k==0) return true;//边界条件,k个容器皆装满
if(cur[k]==target){//当前桶已装满
bool res = backtrack(nums,0,cur,k-1,target,state);//转移桶的状态
m[state] = res;
return res;
}
if(m.count(state)) return m[state];//剪枝
for(int i=start;i<nums.size();i++){
if((state >> i) & 1) continue;//i位数字已被使用,跳过
if(cur[k]+nums[i]>target) continue;//桶溢出,跳过
cur[k]+=nums[i];//做选择
state|= 1<<i;//对第i个数做标记,表示已使用
if(backtrack(nums,i+1,cur,k,target,state)) return true;//递归判断,转移数的状态
//撤销选择
cur[k]-=nums[i];
state^=1<<i;//异或操作将第i位的1置为0
}
return false;
}
};
二. 动态规划
回溯法记忆化搜索的自底向上
class Solution {
public:
bool canPartitionKSubsets(vector<int>& nums, int k) {
int sum = accumulate(nums.begin(), nums.end(), 0);
if (sum % k ) return false;
int per = sum / k;
sort(nums.begin(), nums.end());
if (nums.back() > per) return false;
int n = nums.size();
vector<bool> dp(1 << n, false);//记录所有状态的动态规划表
vector<int> curSum(1 << n, 0);
dp[0] = true;
for (int i = 0; i < 1 << n; i++) {
if (!dp[i]) continue;
for (int j = 0; j < n; j++) {
if (curSum[i] + nums[j] > per)
break;
if (((i >> j) & 1) == 0) {
int next = i | (1 << j);
if (!dp[next]) {
curSum[next] = (curSum[i] + nums[j]) % per;
dp[next] = true;
}
}
}
}
return dp[(1 << n) - 1];
}
};