698. 划分为k个相等的子集 - 力扣(LeetCode)
注意提示:
1 <= k <= len(nums) <= 16
0 < nums[i] < 10000(正整数数组)
数组的和 / k 即可知道每个自己的和是多少,如果不能整除,则返回false。
参考Java(带返回值的)回溯法
// i:开始位置, cw:当前值,target:目标和值, k为组合的个数
// 一次只找到一个就行,找到之后标记位置(help数组)
class Solution {
public:
bool backtrack(int i, int cw, int k, vector &nums, vector &help, int target){
if (k == 0) return true;
if (cw == target){ //找到了匹配
// 匹配下一个
return backtrack(0, 0, k-1, nums, help, target);
}
for (int j = i; j < nums.size(); ++j){
if (help[j] == 0 && cw + nums[j] <= target){
help[j] = 1; //做出选择
if (backtrack(j + 1, cw + nums[j], k, nums, help, target)) return true;
help[j] = 0; //撤销选择
}
}
return false;
}
bool canPartitionKSubsets(vector& nums, int k) {
vector help(nums.size(), 0); // 用于记录nums中的第i个元素有没有被匹配过
int sum = 0;
for(const auto &c : nums){
sum += c;
}
if(sum % k != 0) return false;
int target = sum / k;
return backtrack(0, 0, k, nums, help, target);
}
};
回溯法很好理解,但是一开始自己设计的时候总是没法同意返回值的问题,总是最初的第一遍循环无法执行结束。。。
最后的代码,比上面的容易看懂一些,算法的重点是这一行:
if(cw+nums[j] == target)
flag = backtrack(0, 0, k-1, nums, help, target); //找到了一组,k=k-1,又回到最初开始循环
前两个参数i, cw一定要是0,0,才能回到起点重新开始,我们有help数组记录考察过的值,所以从起点开始并不会重复计算。说的不太清楚,我也是好久才弄明白这儿的。
class Solution {
public:
//i:开始位置, cw:当前值
bool backtrack(int i, int cw,int k, vector &nums, vector &help, int target){
if(k == 0) return true; //目标就是把size元素全装进去
bool flag = false;
for(int j = i; j < nums.size(); ++j){
if(!help[j] && cw + nums[j] <= target){
help[j] = 1; //做出选择
if(cw+nums[j] == target)
flag = backtrack(0, 0, k-1, nums, help, target); //找到了一组,k=k-1,又回到最初开始循环
else
flag = backtrack(j+1, cw+nums[j], k, nums, help, target); //没找到
if(flag) return true;
help[j] = 0; //撤销选择
}
}
return flag;
}
bool canPartitionKSubsets(vector& nums, int k) {
int n = nums.size();
vector help(n, 0); // 用于记录nums中的第i个元素有没有被匹配过
int sum = 0;
int max = 0;
for(const auto &c : nums){
if(max < c) max = c;
sum += c;
}
if(sum % k != 0 || max > sum/k) return false;
int target = sum / k;
return backtrack(0, 0, k, nums, help, target);
}
};
一个月之后回看,当时确实理解的不够,自己又写了一遍回溯:
class Solution {
public:
int target = 0;
vector help;//用于标记nums中对应下标的元素有没有被选取
//idx:开始位置, cw:当前和, k : 还需要找的子集个数
bool backtrack(vector &nums, int k, int idx, int cw){
if(k == 0) return true;
if(cw == target) return backtrack(nums, k-1, 0, 0);//又回到数组的起点重新寻找下一个集合
for(int i = idx; i < nums.size(); ++i){
if(help[i] == 0 && cw + nums[i] <= target){
help[i] = 1;
if(backtrack(nums, k, i+1, cw+nums[i])) return true;
help[i] = 0;
}
}
return false;
}
bool canPartitionKSubsets(vector& nums, int k) {
int sum = accumulate(nums.begin(), nums.end(), 0);
if(sum % k) return false;
int target = sum / k;//目标和值
this->target = target;
help = vector(nums.size(), 0);
return backtrack(nums, k, 0, 0);
}
};
这类回溯不容易理解的地方在于带有返回值