题目
给定一个无重复元素的数组 (candidates) 和一个目标数 (target) ,找出 (candidates) 中所有可以使数字和为 (target) 的组合。
(candidates) 中的数字可以无限制重复被选取。
说明
- 所有数字(包括 target)都是正整数。
- 解集不能包含重复的组合。
当我看到这道题目的时候,当我看到要得到所有结果的组合,我二话没说,立马开始写代码了,一下是我写的代码,写完还美滋滋,心想又是水题的一天,哈哈
class Solution {
vector<vector<int>> res;
vector<int> path;
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
// candidates 里面的元素可以无无限次的使用
// 为了性能的满足,应该做好剪枝
int cur_sum = 0;
dfs(candidates, target, cur_sum);
return res;
}
void dfs(vector<int>& candidates, int target, int cur_sum){
if (cur_sum == target){
res.push_back(path);
return;
}
if (cur_sum > target){
return;
}
for(int i = 0; i < candidates.size(); i++){
cur_sum += candidates[i];
path.push_back(candidates[i]);
dfs(candidates, target, cur_sum);
path.pop_back();
cur_sum -= candidates[i];
}
}
};
在我准备迎接我AC的时候,震惊的突然发现,结果重复了,感觉到这道题目不简单,如下:
输入 : [2,3,6,7] 7
输出 : [[2,2,3],[2,3,2],[3,2,2],[7]]
预期结果: [[2,2,3],[7]]
然后我思考了5分钟.....没怎么发现剪枝的操作,开始看官方答案......
class Solution {
public:
void dfs(vector<int>& candidates, int target, vector<vector<int>>& ans, vector<int>& combine, int idx){
if (idx == candidates.size() || target < 0) {
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;
}
};
官方答案是这样做回溯结构的,对于一个数组,他是线式的进行递归移动的,在回溯(dfs)的参数中有一个参数表示现在我们所考虑的那个选择的下标————(idx)。所谓线式进行递归,是这样的,对于每一次递归我们只对一个选择进行考虑,我们考虑的要选这个数还是不选这个数(to be or not to be),然后下标(idx)不断移动,
我们在回溯递归递进的时候是这样控制的,如果我们选择了这个数,那么我们在下一次递归的时候,还是可以选择这个数,(idx)不移动,如果我们不选择这个数,那么我们再也不能选择这个数了。我们再也不再考虑这个选择了 (index+1),(很巧妙的避免重复的技巧) ,判断idx 是不是到了数组的尽头 进行return。关于线式的递归结构,以前是不怎么熟悉的,现在熟悉了~~哈哈
!!!!但是,这道题目还没完
当我看到上面combine.pop_back();时候我突然觉得这个操作真的很多余啊,想着是否可以把这行注释一下,但是在注释完之后,立马不能AC了,但是我再将combine参数前面的引用删去之后,还是可以AC的,只是AC的时间复杂度,空间复杂度立马上升了很多,如下:
针对这个问题,我和群里朋友展开了讨论,如果不加引用的时候,在递归深入的时候,每次的combine 都需要进行拷贝,所以这样当然时间复杂度必然上升了,至于在combine 的参数之前有引用,而消去那行pop_back(),不能AC的情况,我的解释这样的,因为我们要选择这个数,但是这个数不是必须选的,这个数不是必然正确的数,所以仍然可以被pop_back(),如果不pop_back(),就会加入一些错误的选择,另外因为加了引用之后一直是对一块空间进行修改,不pop_back(),我们目前的答案也就对下一次答案产生影响,下一次答案中存有上一次的答案。所以回溯的模板还是需要有必要遵守的,回溯的那个path 参数还是需要加入引用。做完递归的深入,还是需要对选择进行pop_back()。