Day21 回溯算法:理论基础 77.组合

97 阅读2分钟

理论基础

回溯和递归相辅相成,只要有递归,就有回溯。

回溯其实是一个纯暴力的搜索。

回溯法,一般可以解决如下几种问题:

  • 组合问题:N个数里面按一定规则找出k个数的集合
  • 切割问题:一个字符串按一定规则有几种切割方式
  • 子集问题:一个N个数的集合里有多少符合条件的子集
  • 排列问题:N个数按一定规则全排列,有几种排列方式
  • 棋盘问题:N皇后,解数独等等

你用暴力,一层层for循环嵌套是不好解决这个问题的,也写不出来这样的代码,需要使用回溯搜索法

回溯就是一种暴力的搜索方式。

如果想要清晰了解回溯法,最好把回溯法抽象成图形结构

终止条件:

组合问题,切割问题,排列问题,部分棋盘问题都是在叶子节点,就是收集结果的时候,只有子集问题是在每个节点都要收集结果。

回溯算法模板框架如下:

 void backtracking(参数) {
     if (终止条件) {
         存放结果;
         return;
     }
 ​
     for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
         处理节点;
         backtracking(路径,选择列表); // 递归
         回溯,撤销处理结果
     }
 }

77.组合

题目链接:77.组合

难度指数:😀😐😕

回溯算法就是通过递归来控制有多少层for循环,每一层递归都是一个for循环。

path和result我们定义为全局变量,其实可以放到参数里,然后给它定义为一个引用的方式。

我们不将它们放进递归函数的参数里,是怕参数过多,影响代码的可读性。

16:00

startindex,我们每次递归的时候,会把这次要搜索的起始位置传进来,(初始的时候,当然是传入1啦)

代码思路:

 void backtracking(n, k, startindex) {
     //终止条件
     if (path.size() == k) {  //就到树形结构的叶子节点了  (即收割结果的时候)
         result.push(path);  //result数组把结果收集起来
         return;
     }
     //确定单层递归的逻辑
     for (i = startindex; i <= n; i++) {
         path.push(i);
         backtracking(n, k, i + 1);
         path.pop();
     }
 }

AC代码: (核心代码模式)

 class Solution {
 private:
     vector<vector<int>> result;  //存放符合条件结果的集合
     vector<int> path;  //用来存放符合条件结果 
     void backtracking(int n, int k, int startIndex) {
         if (path.size() == k) {
             result.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) {
         result.clear();  //可以不写
         path.clear();  //可以不写
         backtracking(n, k, 1);
         return result;
     }
 };