目录
一、题目描述
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
- 所有数字(包括 target)都是正整数。
- 解集不能包含重复的组合。
示例 1:
输入:candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]
示例 2:
输入:candidates = [2,3,5], target = 8,
所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]
提示:
- 1 <= candidates.length <= 30
- 1 <= candidates[i] <= 200
- candidate 中的每个元素都是独一无二的。
- 1 <= target <= 500、
二、解题思路
回溯算法的思路和框架就不写了,dfs之后回溯即可。
这里主要想记录下关于在回溯中去重的问题。这道题要求解集中不能有重复的元素,一种直观的想法是先通过回溯把所有的结果找出来,然后在考虑要不要放进结果集返回时在通过排序去重。我就是这么做的……但是这样做会增加对每一个结果都要排序的额外操作,影响效率。
如果做过了本题的相似题目216:组合总数3,也许会有启发。216题中是从数字1-9中去选,且结果不存在重复元素,不含重复组合。如果我们依次按照数字1-9的顺序去选择,并且每次选择下一个数时只能从当前数字往后选,那么就可以在不做任何处理的情况下,做到没有重复元素也没有重复组合。
同理这道题也可以这么操作:先把candidates数组排好序,然后在选择数字之后依次往后选,即可满足题目要求。
ps:在题解区看见了代码随想录大佬给的思路,跟我上面讲的是一样,小开心一下。另外官方题解给了另一种思路,就是对每一个数都有选和不选两个选择,分别对选和不选进行dfs,但是只对选的那个分支进行回溯。如此一来整个构建过程就被拆分成了一个类似二叉树深度遍历的操作,同样可以得到结果,且效率高,唉,比不过比不过……
构建过程如下图(图片来自力扣官方题解)
三、代码实现
把提到的代码都贴一遍吧;方便对比学习
//法一:来自官方题解
class Solution {
public:
void dfs(vector<int>& candidates, int target, vector<vector<int>>& ans, vector<int>& combine, int idx) {
if (idx == candidates.size()) {
return;
}
if (target == 0) {
ans.emplace_back(combine);
return;
}
// 直接跳过
dfs(candidates, target, ans, combine, idx + 1);
// 选择当前数
if (target - candidates[idx] >= 0) {
combine.emplace_back(candidates[idx]);
dfs(candidates, target - candidates[idx], ans, combine, idx);
combine.pop_back();
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> ans;
vector<int> combine;
dfs(candidates, target, ans, combine, 0);
return ans;
}
};
//法二:来自代码随想录
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;
}
// 如果 sum + candidates[i] > target 就终止遍历
for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
sum += candidates[i];
path.push_back(candidates[i]);
backtracking(candidates, target, sum, i);
sum -= candidates[i];
path.pop_back();
}
}
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
result.clear();
path.clear();
sort(candidates.begin(), candidates.end()); // 需要排序
backtracking(candidates, target, 0, 0);
return result;
}
};
//法三:来自渣渣本人。。。
#include <bits/stdc++.h>
using namespace std;
vector<vector<int>> res;
void backtrack(vector<int>& candidates, vector<int>& nums, int target, int sum) {
if (sum == target) {
vector<int> tmp = nums;
//排序去重
sort(tmp.begin(), tmp.end());
if (find(res.begin(), res.end(), tmp) == res.end()) {
res.push_back(tmp);
return;
}
}
for (int i = 0; i < candidates.size(); i++) {
if (sum < target) {
nums.push_back(candidates[i]);
sum += candidates[i];
backtrack(candidates, nums, target, sum);
sum -= candidates[i];
nums.pop_back();
} else
break;
}
}
int main() {
vector<int> candidates = { 2,7,6,3,5,1 };
int target = 9;
combinationSum(candidates, target);
for (auto x : res) {
for (auto i : x) {
cout << i << " ";
}
cout << endl;
}
return 0;
}