回溯算法:理论基础 组合问题

理论基础

  • 回溯算法:一种通过逐步试错的方式来搜索解空间,从而求解问题的算法技术。回溯算法是一种暴力式搜索的方式,通常说的回溯函数也就是递归函数,指的都是一个函数。回溯和递归是相辅相成的。
  • 算法效率:本质是一种穷举式的暴力搜索算法,并不是特别高效,如果想要提升效率,可以进行剪枝操作,可以有限地提升算法效率。即使效率不高,但是有一些问题只能通过暴力搜索解决。
  • 适用情形
    • 组合问题:N个数里面按一定规则找出k个数的集合(不强调元素顺序)
    • 切割问题:一个字符串按一定规则有几种切割方式
    • 子集问题:一个N个数的集合里有多少符合条件的子集
    • 排列问题:N个数按一定规则全排列,有几种排列方式(强调元素顺序)
    • 棋盘问题:N皇后,解数独等等
  • 对回溯算法的理解回溯法解决的问题都可以抽象为树形结构,因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度,都构成的树的深度。递归就要有终止条件,所以必然是一棵高度有限的树(N叉树)
  • 回溯算法模版——回溯三部曲
    1. 返回值以及参数:返回值一般为void。因为回溯算法需要的参数可不像二叉树递归的时候那么容易一次性确定下来,所以一般是先写逻辑,然后需要什么参数,就填什么参数。
    2. 终止条件:一般来说搜到叶子节点了,也就找到了满足条件的一条答案,把这个答案存放起来,并结束本层递归。
    3. 回溯搜索遍历过程:回溯法一般是在集合中递归搜索,集合的大小构成了树的宽度,递归的深度构成的树的深度。
    • for循环就是遍历集合区间,可以理解一个节点有多少个孩子,这个for循环就执行多少次。for循环可以理解是横向遍历,backtracking(递归)就是纵向遍历。
    • //回溯算法模板框架
      void backtracking(参数) {
          if (终止条件) {
              存放结果;
              return;
          }
      
          for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
              处理节点;
              backtracking(路径,选择列表); // 递归
              回溯,撤销处理结果
          }
      }


77.组合

  • 思路:

    • 普通思路是嵌套两层for循环,但是for循环嵌套的方式在层数多的情况下就不好用了,此时就需要回溯算法。

    • 回溯法就用递归来解决嵌套层数的问题,每一次的递归中嵌套一个for循环,那么递归就可以用于解决多层嵌套循环的问题了
    • 使用树形结构来理解回溯如下图:
    • 最开始的集合为[1,2,3,4],第一次取1,集合变为[2,3,4],k为2也就是组合只需要两个数,可以取到集合[1,2] [1,3] [1,4],以此类推。每次从集合中选取元素,可选择的范围随着选择的进行而收缩,调整可选择的范围图中可以发现n相当于树的宽度,k相当于树的深度
    • 只需要把达到叶子节点的结果收集起来,就可以求得 n个数中k个数的组合集合。
  • 回溯三部曲:

    • 返回值以及参数:定义两个数组分别存放符合条件的单一结果和结果集合。参数除了n和k以外,还需要一个int型变量startIndex,用来记录本层递归的中,集合从哪里开始遍历,可以防止出现重复的组合
    • 终止条件:path这个数组的大小如果达到k,说明找到了一个子集大小为k的组合,path存的就是根节点到叶子节点的路径。此时用result二维数组,把path保存起来,并终止本层递归。
    • 单层搜索逻辑:for循环用来横向遍历,递归的过程是纵向遍历。for循环每次从startIndex开始遍历,然后用path保存取到的节点i;调用backtracking不断递归遍历,遇到叶子节点返回;最后进行回溯,撤销本次处理的结果。
  • 时间复杂度: O(n * 2^n)
  • 空间复杂度: O(n)
class Solution {
private:
    vector<int> path;//存放单一结果
    vector<vector<int>> res;//存放结果集
    void backtracking(int n, int k, int startIndex) {
        if(path.size() == k) {  //若当前组合里元素个数已经达到k个
            res.push_back(path);//将该组合放进结果集
            return;
        }

        for(int i = startIndex; i <= n; i++) {
            path.push_back(i);//处理当前节点元素
            backtracking(n, k, i + 1);//递归
            path.pop_back();//回溯
        }
    }

public:
    vector<vector<int>> combine(int n, int k) {
        path.clear();
        res.clear();
        backtracking(n, k, 1);
        return res;
    }
};

总结

回溯法就是解决这种k层for循环嵌套的问题

回溯法的搜索过程可以抽象为树形结构

参考链接

代码随想录:理论基础  组合问题

  • 15
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值