「这是我参与11月更文挑战的第23天,活动详情查看:2021最后一次更文挑战」
40. 组合总和 II
题目描述
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
注意: 解集不能包含重复的组合。
示例 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次,所以需要进行去重处理。
组合问题可以抽象为树结构
- 对于树:
横向为一层,即横着所有元素在一个树层
竖向为一个分支,即竖着的所有元素在一个树枝
需要判断的是:
相同的元素是在一层中使用的还是在一个数值上使用的
在同一个树枝上是可以的,但是在同一树层的话不行
难点:
去重处理(去除重复的组合)
可以使用set来去重,但是容易超时
所以需要在搜索的过程中对同一层使用过的元素进行去重
回溯法三部曲
1、确定递归函数的参数与返回值
相对于上一题,本题需要多添加一个bool类型的数组used来判断同一层中的元素是否使用过
2、递归终止条件
sum == target
sum > target
3、单层搜索的逻辑
这里需要进行去重处理,判断相同的元素是否在同一层中
对于在同一个树枝上的相同元素不进行去重
如果:
candidates[i] == candidates[i-1] , 并且 used[i-1] == false ,
则说明前一个树枝已经使用过了 candidates[i-1] ,也就是同一层已经使用过了元素candidates[i-1]
此时需要结束本次循环,进行下一个循环(continue)
代码
class Solution
{
public:
vector<vector<int>> combinationSum2(vector<int> &candidates, int target)
{
result.clear();
path.clear();
// used数组元素全部初始化为false
vector<bool> used(candidates.size(), false);
//排序
sort(candidates.begin(), candidates.end());
backtracking(candidates, target, 0, 0, used);
return result;
}
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){
// return;
// }
if (sum == target)
{
result.push_back(path);
}
// for循环中进行剪枝操作
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]使用过
// 对同一层中使用过的元素去重
if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false)
{
// 同一层中已经使用过了candidates[i]元素,所以跳过
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();
}
}
};