给定一个整数数组 nums 和一个正整数 k,找出是否有可能把这个数组分成 k 个非空子集,其总和都相等。
示例 1:
输入: nums = [4, 3, 2, 3, 5, 2, 1], k = 4
输出: True
说明: 有可能将其分成 4 个子集(5),(1,4),(2,3),(2,3)等于总和。
注意:
1 <= k <= len(nums) <= 16
0 < nums[i] < 10000
想不出来什么好的办法,看看网上 吧。
你看,既然每个子集的和都是相等的,那么,就去凑好了。另外就是剪枝,剪枝是重要的:
在所有预备工作完成后,你如何进行递归呢?如果可以递归的话,你需要首先定义这个递归函数是干什么用的,而所有的递归,都是一个树状结构,递归到最后,就是一个解,就是这样。所以我递归就是,如果不是最后的解,我就接着递归下去,如果是最后的解,就进行判断,对就是这样。
自己写的代码总是不通过,也不知道是怎么回事,Line 17: Char 10: runtime error: load of value 253, which is not a valid value for type 'bool' (__Serializer__.cpp)
class Solution {
int l_n; // 数组长度
int l_k; // 一个子集的总和
int l_m; // 划分子集个数
vector<vector<int>> l_res; // 结果判断数组
public:
// 递归到最后进行判断
bool function(vector<int>& nums, int c_i){
bool res; // 作为未递归到最后时的结果
// 如果到了最后,进行判断
if(c_i == l_n){
for(int i=0; i<l_m; i++){ // 一共m个子集
int sum = 0; // 子集的和
cout<<i<<":";
for(int j=0; j<l_res[i].size(); j++){
sum += l_res[i][j];
cout<<l_res[i][j]<<" ";
}
cout<<endl;
if(sum != l_k) return false; // 如果有一个不是就返回false
}
cout<<"\n_____________\n";
return true;
}
else{ // 如果不是最后的,就继续向下递归,就是随便插入一个,最后取出
for(int i=0; i<l_m; i++){
l_res[i].push_back(nums[c_i]);
res |= function(nums, c_i+1); // 递归到下一层并输出结果
l_res[i].pop_back();
}
}
return res;
}
bool canPartitionKSubsets(vector<int>& nums, int c_m) {
l_m = c_m; // 设定初始变量
l_n = nums.size();
if(l_n == 0) return false; // 如果长度为0返回false
int max = nums[0]; // 最大值
int sum = 0; // 数组总和
for(int i=0; i<l_n; i++){ // 进行总和计算和最大值获取
sum += nums[i];
if(max < nums[i]) max = nums[i];
}
if((sum%l_m) != 0) return false; // 如果每一个的总和不是整数自然不行
l_k = sum/l_m;
if(max > l_k) return false; // 如果最大元素还大于子集总和自然不行
for(int i=0; i<l_m; i++) l_res.push_back({}); // 初始化res数组
// 下面进行递归
return function(nums, 0);
}
};
下面是网上的做法,写的真好,或者说,思想真好,只有成功才执行下一个递归,这种方法值得借鉴,套娃式递归。
class Solution {
bool backtracking(vector<int> &nums, int k, int target, int cur, int start, bool* used) {
// 返回条件
if (k == 0) return true; // 这个k就是划分的多少个集合
if (cur == target) { // target就是一个子集合多大
// 如果本集合成功,构建下一个集合,下面的就不管了
return backtracking(nums, k-1, target, 0, 0, used);
}
for (int i = start; i < nums.size(); i++) { // 从start开始到末尾
if (!used[i] && cur+nums[i] <= target) {// 如果没有使用并且小于等于
used[i] = true; // 就使用它
if (backtracking(nums, k, target, cur+nums[i], i+1, used)) return true;
used[i] = false; // 回归原位置
}
}
return false;
}
public:
bool canPartitionKSubsets(vector<int> nums, int k) {
// 注意nums[i] > 0
int sum = 0, maxNum = 0;
for (int i = 0; i < nums.size(); i++) {
sum += nums[i];
if (maxNum < nums[i]) maxNum = nums[i];
}
if (sum % k != 0 || maxNum > sum/k) return false; // 常规避险
bool* used = new bool[nums.size()]; // 一个使用标记变量
return backtracking(nums, k, sum/k, 0, 0, used);
}
};