回溯算法总结篇

定义

首先先来了解一下回溯的定义吧

回溯是递归的副产物,只要有递归就会有回溯

所以回溯经常和二叉树,深度优先遍历等操作联系到一起,因为他们都用到了递归。
回溯本质上是暴力搜索,效率并不高,最多剪枝一下。

回溯法可以解决的问题

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

回溯模板(重点)

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

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

组合问题

用递归控制for循环嵌套的数量
搜索的过程:for循环横向遍历,递归纵向遍历,回溯不断调整结果集。

剪枝精髓是:for循环在寻找起点的时候要有一个范围,如果这个起点到集合终止之间的元素已经不够题目要求的k个元素了,就没有必要搜索了。

例题:
组合总和3
本题的剪枝会好想一些,即:已选元素总和如果已经大于n(题中要求的和)了,那么往后遍历就没有意义了,直接剪掉
组合问题2
本题没有数量要求,可以无限重复,但是有总和的限制,所以间接的也是有个数的限制。
所以需不需要startIndex呢?
如果是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex,例如:电话号码的字母组合
注意以上只是说求组合的情况,如果是排列问题,又是另一套分析的套路
组合问题2
本题涉及到去重问题,代码随想录提到了树枝去重和树层去重,都知道组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上“使用过”,一个维度是同一树层上“使用过”。
回溯算法总结篇_第1张图片
可以看出在candidates[i] == candidates[i - 1]相同的情况下:

  • used[i - 1] == true,说明同一树支candidates[i - 1]使用过
  • used[i - 1] == false,说明同一树层candidates[i - 1]使用过

切割问题

分割回文串
本题有以下难点

  • 切割问题其实类似组合问题
  • 如何模拟那些切割线
  • 切割问题中递归如何终止
  • 在递归循环中如何截取子串
  • 如何判断回文

回溯算法总结篇_第2张图片

子集问题

子集
模板题
在树形结构中子集问题是要收集所有节点的结果,而组合问题是收集叶子节点的结果。
本题其实可以不需要加终止条件,因为startIndex >= nums.size(),本层for循环本来也结束了,本来我们就要遍历整颗树
子集2
开始涉及去重,还是上文说到的树枝去重和树层去重,用一个used数组,一样的套路。

排列问题

排列是有序的,也就是说[1,2] 和[2,1] 是两个集合,这和之前分析的子集以及组合所不同的地方。
元素1在[1,2]中已经使用过了,但是在[2,1]中还要在使用一次1,所以处理排列问题就不用使用startIndex了。
全排列
排列问题的不同:

  • 每层都是从0开始搜索而不是startIndex
  • 需要used数组记录path里都放了哪些元素了

N皇后问题

N皇后
棋盘的宽度就是for循环的长度,递归的深度就是棋盘的高度,这样就可以套进回溯法的模板里了。

解数独

解数独
这道题是回溯法中比较难的。
但理解 “二维递归”这个过程,发现就没那么难了。N皇后问题 (opens new window)是因为每一行每一列只放一个皇后,只需要一层for循环遍历一行,递归来遍历列,然后一行一列确定皇后的唯一位置。本题就不一样了,本题中棋盘的每一个位置都要放一个数字,并检查数字是否合法,解数独的树形结构要比N皇后更宽更深。

总结

好好做完上面那些问题,可以回答下:

  • 如何理解回溯法的搜索过程?
  • 什么时候用startIndex,什么时候不用?
  • 如何去重?如何理解“树枝去重”与“树层去重”?
  • 去重的几种方法?
  • 如何理解二维递归?

你可能感兴趣的:(总结,算法,leetcode,回溯,深度搜索)