【40讲系列9】回溯算法、剪枝
一、理论
1)首先,使用回溯算法关键是,将问题转化为 【树形问题】。
2)回溯的关键点: for循环、 递归。
for循环的作用在于另寻它路,可以逐个选择当前节点下的所有可能往下走下去的分支路径。
递归可以实现一条路走到黑和回退一步,把递归放在for循环内部,那么for每一次的循环,都在给出一个路径后进入递归,继续往下走。
3)因此,for循环和递归配合可以实现回溯,所以DFS是最典型的回溯法的应用。
理论详解及分类经典习题:回溯算法入门级详解 + 练习(持续更新)
二、典型例题
☆☆☆☆①:如何使N个皇后彼此之间不能相互攻击(LC51、LC52)
思路:如果在(i,j)处放置了一个皇后,那么
1. 整个第 i 行的位置都不能放置;
2. 整个第 j 列的位置都不能放置。
3. 如果位置(a,b)满足|a-i|==|b-j|,说明(a,b)和(i,j)处在同一条斜线上,也不能放置。
class Solution { // LeetCode 52 // 参考左神,还可以进一步使用位运算加速 int res = 0; public int totalNQueens(int n) { if (n < 1) return 0; int[] record = new int[n]; // record[index]表示第index行皇后所在的列数 help(record,0,n); return res; } private void help(int[] record,int index, int n){ if (index == n){ // 此时已经确定一种情况 res++; return; } for (int j = 0; j < n; j++) { if (isVaild(record,index,j)){ record[index] = j; help(record,index+1,n); } } } private boolean isVaild(int[] record,int index, int j){ for (int k = 0; k < index; k++) { if (record[k] == j || Math.abs(k - index) == Math.abs(record[k] - j)){ return false; } } return true; } }
Note:本题的最优解是使用位运算加速,见【40讲系列11】位运算
②:判断数独是否有效(LC36)
M
☆☆☆☆③:解数独(LeetCode37. 解数独)
三、扩展例题
第一组(树形问题):LeetCode17. 电话号码的字母组合、LeetCode93. 复原IP地址、LeetCode131. 分割回文串
第二组(排列问题):LeetCode46. 全排列、LeetCode47. 全排列 II
第三组(组合问题):LeetCode77. 组合、LeetCode39. 组合总和、LeetCode40. 组合总和 II、LeetCode216. 组合总和 III、LeetCode78. 子集、LeetCode90. 子集 II、LeetCode401. 二进制手表
第四组(二维平面上的回溯):LeetCode79. 单词搜索
第五组(Flood Fill):LeetCode200. 岛屿数量、LeetCode130. 被围绕的区域、LeetCode417. 太平洋大西洋水流问题
第六组(游戏问题):LeetCode51. N 皇后、LeetCode52. N皇后 II、LeetCode37. 解数独
四、综合练习
题型一:排列、组合、子集相关问题
Note:理解为什么有时候用visited数组,有时候设置搜索起点start
- LeetCode46. 全排列
- LeetCode47. 全排列2:思考为什么造成了重复,如何在搜索之前就判断这一支会产生重复;
- LeetCode39. 组合总和
- LeetCode40. 组合总和2
- LeetCode77. 组合
- LeetCode78. 子集
- LeetCode90. 子集2 : 剪枝技巧同 47 题、39 题、40 题;
- LeetCode60. 第k个排列 : 利用了剪枝的思想,减去了大量枝叶,直接来到需要的叶子结点;
- LeetCode93. 复原IP地址
题型二:Flood Fill
Note:以下问题都不建议修改输入数据,设置visited数组是标准做法。
- LeetCode733. 图像渲染
- LeetCode200. 岛屿数量
- LeetCode130. 被围绕的区域
- LeetCode79. 单词搜素
题型三:字符串中的回溯问题
Note: 字符串问题的特殊之处在于,字符串的拼接会生成新对象,因此没有显示【回溯】的过程。但如果用StringBuilder拼接字符串则需要回溯。
- LeetCode17. 电话号码的字母组合
- LeetCode784. 字母大小写全排列
- LeetCode22. 括号生成:本题也可用BFS,可以通过本题理解为什么回溯算法都是DFS,并且都用递归来写。
题型四:游戏问题
- LeetCode51. N皇后:其实就是全排列问题,注意设计清楚状态变量,在遍历的时候需要记住一些信息,空间换时间;
- LeetCode37. 解数独:思路同N皇后问题
- LeetCode488. 祖玛游戏
- LeetCode529. 扫雷游戏