• 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()。

  • 相关阅读:
    Place the Robots 需要较强的建图能力
    Channel Allocation 贪心涂色
    A Simple Math Problem 矩阵打水题
    按钮的高亮状态的颜色
    设置UITableViewCell选中颜色但是无效
    常用的忽略警告
    UIButton按钮的高亮状态颜色
    字节的不同单位间的转换
    通过颜色绘制图片UIImage
    头像裁剪功能的实现
  • 原文地址:https://www.cnblogs.com/wsl-hitsz/p/13642601.html
Copyright © 2020-2023  润新知