引言
本文主要讲解一下回溯算法、动态规划、滑动窗口、二分查找等几种典型算法的框架思想和解题公式,只要掌握了各种类型算法的框架思想,根据下面总结好的公式带入即可,轻松秒杀leetCode面试题!
回溯算法
框架思想
回溯的核心思想:是通过遍历一系列可能的选择,并在每一次选择中确认是否满足约束条件,如果满足条件则继续下一步,否则回退到上一步,重新选择其他可能的路径,直到找到问题的解决方案。
解决一个回溯问题,实际上是一次暴力搜索的过程,也是一次决策树的遍历过程,涉及到做选择相关的,大部分都可用回溯算法来解决问题,是一个使用场景较广的算法。
回溯三要素:
1、已选路径:顾名思义,就是已经做出的选择。
2、选择列表:也就是你当前可以做的选择。
3、结束条件:到达决策树最底层了,或已经找到本次的解,不需要再继续做选择的条件。
- 剪枝小Tips
- 由于回溯算法,是要进行完整决策链路的遍历,算法复杂度会比较高,所以需要一些必要的剪枝操作,去掉整个过程中的无效决策,以减少回溯次数。
- 比如,选择列表中有重复的数据,可以通过先将选择列表排序,再结合标识位的适用的方式来跳过重复数据的回溯遍历。
- 回溯框架
def backtrack(路径, 选择列表):
if 触发结束条件:
result.add(路径)
return
for 选择 in 选择列表:
(这里可以剪枝优化)
做选择
backtrack(路径, 选择列表)
撤销选择
应用场景
排列组合子集问题
这里以全排列为例,引出回溯算法。
比方说给三个数 [1,2,3],我们也知道 n 个不重复的数,全排列共有 n! 个。
- 三要素映射
- 初始的选择列表就是【1,2,3】。
- 比如说第一次选择了1,接着下一次选择了2,那么路径就是【1,2】。
- 结束条件:就是已经没有可以提供的选择了。
- 明确三要素之后,我们就可以动手写代码了,注意每次选择,不能用已经用过的选择了,这个也比较简单,用一个标识位数组 boolean[] 标志一下就可以了,路径通常用一个链条这种数据结构来记录,应为撤销上次选择操作比较方便。
- 同理,组合和子集问题也是类似的,唯一不同的是每次选择列表不同而已,这里就不再展示。
N皇后问题
这是一个比较经典的问题了,当 N = 8 时,就是八皇后问题,著名数数学家高斯穷尽一生都没有数清楚八皇后问题到底有几种可能的放置方法。这个问题的复杂度确实非常高,最坏时间复杂度仍然是 O(N^(N+1))。
- N皇后介绍
给定一个N×N的棋盘,要求找到所有合法的N个皇后的摆放方式,使得它们不互相攻击。也就是说,每个皇后应该在棋盘上的不同行和不同列,并且不能处于同一斜线上。
- 这是一个8皇后的摆放例子,也就是 N=8 的case。
Q . . . . . . .
. . . . Q . . .
. . . . . . . Q
. . . . . Q . .
. . Q . . . . .
. . . . . . Q .
. Q . . . . . .
. . . Q . . . .
这个问题本质上跟全排列问题差不多,决策树的每一层表示棋盘上的每一行;每个节点可以做出的选择是,在该行的任意一列放置一个皇后。
- 三要素
- 选择列表:每一行的N个位置。
- 结束条件:最后一行选择完毕。
- 路径:每次选择的位置集合。
- 弄清楚回溯算法的三要素之后,我们就可以写代码逻辑了。
public class NQueens {
public List<List<String>> solveNQueens(int n) {
//所有摆放位置的总集合
List<List<String>> solutions = new ArrayList<>();
//表示棋盘
char[][] board = new char[n][n];
for (int i = 0; i < n; i++) {
Arrays.fill(board[i], '.');
}
backtrack(board, 0, solutions);
return solutions;
}
// 路径:board 中小于 row 的那些行都已经成功放置了皇后
// 选择列表:第 row 行的所有列都是放置皇后的选择
// 结束条件:row 超过 board 的最后一行
private void backtrack(char[][] board, int row, List<List<String>> solutions) {
//结束条件:已经选到最后一行了
if (row == board.length) {
solutions.add(construct(board));
return;
}
//选择列表:这一行的n个位置
for (int col = 0; col < board.length; col++) {
//isValid 校验当前选择是否合法
if (isValid(board, row, col)) {
//在棋盘记录皇后位置
board[row][col] = &