40. 组合总和 II
给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的每个数字在每个组合中只能使用 一次 。(坑:每个数字是指每个下标位置上的数字,这会产生重复)
注意:解集不能包含重复的组合。
重复集合不可重复选取每个位置上的数字(深度去重中的递归去重)
重复集合不可返回重复的组合答案(宽度去重中的特判)
深度去重:
1.递归去重:(去重复下标,不在同一下标取值)任意集合只要不可重复选取当前位置元素时就要应用,但并不保证是否组合有重复元素,因为集合可能本身含重复值,体现在startindex=i+1
2.特判去重(去重复值,哪怕是来自不同下标):必须是含重复值的集合且该集合要求返回的组合里不含有重复元素,体现在基于1的情况下额外特判:
1.if (i == startIndex && candidates[i] == candidates[i - 1])continue;
2.if (i > 0 && used[i - 1] == true && candidates[i] == candidates[i - 1] ) continue;
宽度去重:
1.特判去重:(去除重复组合,哪怕来自不同下标),必须是含重复值的集合并且该集合要求返回的组合间不能相同.1.if (i != startIndex && candidates[i] == candidates[i - 1])continue;
2.if (i > 0 && used[i - 1] == false && candidates[i] == candidates[i - 1] ) continue;
3.unordered_set uset;if (uset.find(nums[i]) != uset.end()) continue;
示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]
示例 2:
输入: candidates = [2,5,2,1,2], target = 5,
输出:
[
[1,2,2],
[5]
]
提示:
1 <= candidates.length <= 100
1 <= candidates[i] <= 50
1 <= target <= 3
startindex和used数组都是大同小异,来判定当前相同的节点是否在树层上(即第一个树枝上)
使用startIndex去重的逻辑:是否自己在树层上(第一个树枝上),如果是,则去掉。条件大于设置为i != index更容易理解
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int target, int sum, int startIndex) {
if (sum == target) {//递归的深度取决于目标值,组合的长度是任意的.
result.push_back(path);
return;
}
for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
// 树层宽度去重就是for去重(重复的集合要产生不可重复的组合时应用)
// 宽度层的startindex都小于当前i(不等于),深度层的startindex都等于当前i
if (i != startIndex && candidates[i] == candidates[i - 1]) {
continue;
//宽度去重的原因:会导致出现多个相同组合
//{1,1,2,3}第二个1能取到的组合,第一个1也能取到
//"第二个1的第一轮选第二个1,还剩下{2,3}"与"第一个1第一轮选第一个1,还剩下{2,3}"产生的后续组合是一个效果,即第二个1产生的结果第一个1有,不能产生的结果第一个1还有.
}
sum += candidates[i];
path.push_back(candidates[i]);
//树枝深度去重之一就是递归去重(任意集合只要不可重复选取当前位置元素时应用)
//还有一种深度去重就是i==startindex,用于重复集合里不可重复选取元素.
backtracking(candidates, target, sum, i + 1); //深度-递归函数去重:不可重复选取当前下标,i后移
sum -= candidates[i];
path.pop_back();
}
}
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
path.clear();result.clear();
// 排序可以让相同的元素都挨在一起。
sort(candidates.begin(), candidates.end());
backtracking(candidates, target, 0, 0);
return result;
}
};
使用used去重的逻辑:遍历到当前节点时,若前面一个值与我相等,判定是否还在同一个树枝上,(在的话说明自己初于递归层(深度层,树枝)中)如果不是,则证明自己在树层上,满足树层去重
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int target, int sum, int startIndex, vector<bool>& used) {
if (sum == target) {
result.push_back(path);
return;
}
for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
// used[i - 1] == true,说明同一树枝candidates[i - 1]使用过,或者说明前一个元素正处于递归层(深度层),当前元素是递归过程中的产物,跳过属于重复集合里的深度去重,即不产生内含相同值的组合
// used[i - 1] == false,说明同一树层candidates[i - 1]使用过,或者说明前一个元素没有处于递归层,即当前元素是for过程(宽度层)的产物,跳过属于重复集合里的宽度去重,即不产生重复组合
// 要对同一树层使用过的元素进行跳过
if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) {
continue;
}
sum += candidates[i];
path.push_back(candidates[i]);
used[i] = true;
backtracking(candidates, target, sum, i + 1, used);
used[i] = false;
sum -= candidates[i];
path.pop_back();
}
}
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
path.clear();result.clear();
sort(candidates.begin(), candidates.end());
vector<bool> used(candidates.size(), false);
backtracking(candidates, target, 0, 0, used);
return result;
}
};