leetcode 39 组合总数(回溯)

题目

给定一个无重复元素的数组 \(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()。

posted @ 2020-09-09 22:30  wsl-hitsz  阅读(180)  评论(0编辑  收藏  举报