21.回溯(1) | 77. 组合

        今天终于结束了二叉树,开始回溯章节。主要内容是回溯的理论基础和基本代码模板,需要注意一定要将一直保持不变的vector设置为全局变量,否则有可能超时。


        回溯的理论基础在二叉树的后序递归遍历和相关题目中已经涉及到,这里主要总结下回溯法的应用场景(整理自代码随想录):

  • 组合问题:N个数里面按一定规则找出k个数的集合(与顺序无关)
  • 排列问题:N个数按一定规则全排列,有多少种排列方式(与顺序有关)
  • 分割问题:一个字符串按一定规则有多少种分割方式
  • 子集问题:一个N个数的集合里有多少符合条件的子集
  • 棋盘问题:N皇后,数独

其中“组合”与“排列”问题的区别在于:组合不区分顺序,只要内部元素相同,就认定为同一个组合;而对于排列,即便元素相同,只要内部顺序不同,就是2个不同的排列。


        今天仅有的一道题(77. 组合)是回溯法的模板。分别用全局变量path和res保存当前路径中数字和所有结果。递归函数出口为当前path的长度已经达到k。回溯函数参数除n和k以外,还需当前层的循环起始值begin,每层回溯函数都从begin开始遍历到n为止。

public:
    vector<int> path;
    vector<vector<int>> res;
    void back(int begin, int n, int k) {
        if (path.size() == k) {
            res.push_back(path);
            return;
        }
        for (int i = begin; i <= n; ++i) {
            path.push_back(i);
            back(i + 1, n, k);
            path.pop_back();
        }
        return;
    }
    vector<vector<int>> combine(int n, int k) {
        path.clear();
        res.clear();
        back(1, n, k);
        return res;
    }
};

由于之前已经接触过回溯,这一题实现比较容易。需要注意在回溯前后要分别向path中添加,删除 元素。

        自己之前在其他题目中还实现过另外一种回溯方法,将上面的“在每次回溯中横向遍历所有下一个数字的可能”变为了“将每个数字都分为‘取’和‘不取’这2种可能”。相比第一种方法,递归出口还需多一个当前下标超过n。起初实现后当数字n和k取较大值时运行超时,找到原因是因为将path也作为了参数传递,如果像上面一样把path作为全局变量,就不会超时。

class Solution {
public: 
    vector<vector<int>> res;
    vector<int> path;
    void back(int begin, int n, int k) {
        if (path.size() == k) {
            res.push_back(path);
            return;
        }
        if (begin > n) {
            return;
        }
        path.push_back(begin);
        back(begin + 1, n, k);
        path.pop_back();
        back(begin + 1, n, k);
        return;
    }
    vector<vector<int>> combine(int n, int k) {
        path.clear();
        res.clear();
        back(1, n, k);
        return res;
    }
};

在自己的编译器中用全局变量cnt统计了n和k分别取20和16时,递归函数的运行次数,发现该方法的cnt约是第一种方法cnt的2倍左右。分析原因,发现这种方法在不取某个值时,也会进行递归,而第一种方法则不会,效率更高。所以之后的回溯将使用第一种方法。最后,还需要注意第一种方法回溯函数的形参begin对应的实参应该是i + 1,而第2种是begin + 1。这是因为第一种方法在遍历到i时,说明i及之前的数都不应该再取到了,而第2种方法只是继续决定下一个数取或者不取。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值