LeetCode刷题指南
第 0 章 hot100
0.1 哈希
0.2 双指针
0.3 滑动窗口
0.4 子串
0.5 普通数组
0.6 矩阵
0.7 链表
0.8 二叉树
0.9 图论
0.10 回溯
0.11 二分查找
0.12 栈
0.13 堆
0.14 贪心算法
0.15 动态规划
0.16 多维动态规划
0.17 技巧
第0-1章 面试经典150
0.1 数组/字符串
0.2 双指针
0.3 滑动窗口
链表
二叉树
第 1 章 最易懂的贪心算法
1.1 算法解释
1.2 分配问题
1.3 区间问题
1.4 练习
第 2 章 玩转双指针
2.1 算法解释
2.2 Two Sum
2.3 归并两个有序数组
2.4 滑动窗口
2.5 快慢指针
2.6 练习
第 3 章 居合斩!二分查找
3.1 算法解释
3.2 求开方
3.3 查找区间
3.4 查找峰值
3.5 旋转数组查找数字
3.6 练习
第 4 章 千奇百怪的排序算法
4.1 常用排序算法
4.2 快速选择
4.3 桶排序
4.4 练习
第 5 章 一切皆可搜索
5.1 算法解释
5.2 深度优先搜索
5.3 回溯法
5.4 广度优先搜索
5.5 练习
第 6 章 深入浅出动态规划
6.1 算法解释
6.2 基本动态规划:一维
6.3 基本动态规划:二维
6.4 分割类型题
6.5 子序列问题
6.6 背包问题
6.7 字符串编辑
6.8 股票交易
6.9 练习
第 7 章 化繁为简的分治法
7.1 算法解释
7.2 表达式问题
7.3 练习
第 8 章 巧解数学问题
8.1 引言
8.2 公倍数与公因数
8.3 质数
8.4 数字处理
8.5 随机与取样
8.6 练习
第 9 章 神奇的位运算
9.1 常用技巧
9.2 位运算基础问题
9.3 二进制特性
9.4 练习
第 10 章 妙用数据结构
10.1 C++ STL
10.2 Python 常用数据结构
10.3 数组
10.4 栈和队列
10.5 单调栈
10.6 优先队列
10.7 双端队列
10.8 哈希表
10.9 多重集合和映射
10.10 前缀和与积分图
10.11 练习
第 11 章 令人头大的字符串
11.1 引言
11.2 字符串比较
11.3 字符串理解
11.4 字符串匹配
11.5 练习
第 12 章 指针三剑客之一:链表
12.1 数据结构介绍
12.2 链表的基本操作
12.3 其它链表技巧
12.4 练习
第 13 章 指针三剑客之二:树
13.1 数据结构介绍
13.2 树的递归
13.3 层次遍历
13.4 前中后序遍历
13.5 二叉查找树
13.6 字典树
13.7 练习
第 14 章 指针三剑客之三:图
14.1 数据结构介绍
14.2 二分图
14.3 拓扑排序
14.4 练习
第 15 章 更加复杂的数据结构
15.1 引言
15.2 并查集
15.3 复合数据结构
15.4 练习
第16章 面试题
第 17 章 十大经典排序算法
README
本文档使用 MrDoc 发布
-
+
首页
5.3 回溯法
# 5.3 回溯法 `回溯法`(backtracking)是优先搜索的一种特殊情况,又称为试探法,常用于需要记录节点状态的深度优先搜索。通常来说,排列、组合、选择类问题使用回溯法比较方便。 顾名思义,回溯法的核心是回溯。在搜索到某一节点的时候,如果我们发现目前的节点(及其子节点)并不是需求目标时,我们回退到原来的节点继续搜索,并且`把在目前节点修改的状态还原`。这样的好处是我们可以始终只对图的总状态进行修改,而非每次遍历时新建一个图来储存状态。在具体的写法上,它与普通的深度优先搜索一样,都有 [修改当前节点状态]→[递归子节点] 的步骤,只是多了回溯的步骤,变成了 [修改当前节点状态]→[递归子节点]→[回改当前节点状态]。 没有接触过回溯法的读者可能会不明白我在讲什么,这也完全正常,希望以下几道题可以让您理解回溯法。如果还是不明白,可以记住两个小诀窍,`一是按引用传状态,二是所有的状态修改在递归完成后回改`。 回溯法修改一般有两种情况,一种是修改最后一位输出,比如排列组合;一种是修改访问标记,比如矩阵里搜字符串。 ## [46. Permutations](https://leetcode.com/problems/permutations/) ### 题目描述 给定一个无重复数字的整数数组,求其所有的排列方式。 ### 输入输出样例 输入是一个一维整数数组,输出是一个二维数组,表示输入数组的所有排列方式。 ``` Input: [1,2,3] Output: [[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,2,1], [3,1,2]] ``` 可以以任意顺序输出,只要包含了所有排列方式即可。 ### 题解 怎样输出所有的排列方式呢?对于每一个当前位置 i,我们可以将其于之后的任意位置交换,然后继续处理位置 i+1,直到处理到最后一位。为了防止我们每此遍历时都要新建一个子数组储存位置 i 之前已经交换好的数字,我们可以利用回溯法,只对原数组进行修改,在递归完成后再修改回来。 我们以样例 [1,2,3] 为例,按照这种方法,我们输出的数组顺序为 [[1,2,3], [1,3,2], [2,1,3], [2,3,1],[3,2,1], [3,1,2]],可以看到所有的排列在这个算法中都被考虑到了。 ```py class Solution: def bt(self, nums, i, res): n = len(nums) if i == n: res.append(nums[:]) return for j in range(i,n): nums[i], nums[j] = nums[j], nums[i] self.bt(nums, i+1, res) nums[i], nums[j] = nums[j], nums[i] def permute(self, nums: List[int]) -> List[List[int]]: res = [] self.bt(nums, 0, res) return res ``` ## [77. Combinations](https://leetcode.com/problems/combinations/) ### 题目描述 给定一个整数 n 和一个整数 k,求在 1 到 n 中选取 k 个数字的所有组合方法。 ### 输入输出样例 输入是两个正整数 n 和 k,输出是一个二维数组,表示所有组合方式。 ``` Input: n = 4, k = 2 Output: [[2,4], [3,4], [2,3], [1,2], [1,3], [1,4]] ``` 这里二维数组的每个维度都可以以任意顺序输出。 ### 题解 类似于排列问题,我们也可以进行回溯。排列回溯的是交换的位置,而组合回溯的是是否把当前的数字加入结果中。 ```py class Solution: def combine(self, n: int, k: int) -> List[List[int]]: res = [] path = [] def dfs(i): if len(path) == k: res.append(path.copy()) return for j in range(i,n+1): path.append(j) dfs(j+1) path.pop() dfs(1) return res ``` ## [79. Word Search](https://leetcode.com/problems/word-search/) ### 题目描述 给定一个字母矩阵,所有的字母都与上下左右四个方向上的字母相连。给定一个字符串,求字符串能不能在字母矩阵中寻找到。 ### 输入输出样例 输入是一个二维字符数组和一个字符串,输出是一个布尔值,表示字符串是否可以被寻找到。 ``` Input: word = "ABCCED", board = [[’A’,’B’,’C’,’E’], [’S’,’F’,’C’,’S’], [’A’,’D’,’E’,’E’]] Output: true ``` 从左上角的’A’ 开始,我们可以先向右、再向下、最后向左,找到连续的"ABCCED"。 ### 题解 不同于排列组合问题,本题采用的并不是修改输出方式,而是修改访问标记。在我们对任意位置进行深度优先搜索时,我们先标记当前位置为已访问,以避免重复遍历(如防止向右搜索后又向左返回);在所有的可能都搜索完成后,再回改当前位置为未访问,防止干扰其它位置搜索到当前位置。使用回溯法时,我们可以只对一个二维的访问矩阵进行修改,而不用把每次的搜索状态作为一个新对象传入递归函数中。 ```py class Solution: def exist(self, board: List[List[str]], word: str) -> bool: m, n = len(board), len(board[0]) def dfs(i,j,k): if k == len(word): return True if not (0<=i<m and 0<=j<n and board[i][j] == word[k]): return False temp = word[k] board[i][j] = '' res = dfs(i+1,j,k+1) or \ dfs(i,j+1,k+1) or \ dfs(i-1, j, k+1) or \ dfs(i, j-1, k+1) board[i][j] = temp return res for i in range(m): for j in range(n): if dfs(i,j,0): return True return False ``` ## [51. N-Queens](https://leetcode.com/problems/n-queens/) ### 题目描述 给定一个大小为 n 的正方形国际象棋棋盘,求有多少种方式可以放置 n 个皇后并使得她们互不攻击,即每一行、列、左斜、右斜最多只有一个皇后。 <figure>  <figcaption>题目 51 - 八皇后的一种解法</figcaption> </figure> ### 输入输出样例 输入是一个整数 n,输出是一个二维字符串数组,表示所有的棋盘表示方法。 ``` Input: 4 Output: [ [".Q..", // Solution 1 "...Q", "Q...", "..Q."], ["..Q.", // Solution 2 "Q...", "...Q", ".Q.."] ] ``` 在这个样例中,点代表空白位置,Q 代表皇后。 ### 题解 类似于在矩阵中寻找字符串,本题也是通过修改状态矩阵来进行回溯。不同的是,我们需要对每一行、列、左斜、右斜建立访问数组,来记录它们是否存在皇后。这里如果我们通过对每一行/列遍历来插入皇后,我们就不需要对行/列建立访问数组了。 ```py # 辅函数。 def backtracking(solutions: List[List[str]], board: List[List[str]], column: List[bool], ldiag: List[bool], rdiag: List[bool], row: int): n = len(board) if row == n: solutions.append(["".join(row) for row in board]) return for i in range(n): if column[i] or ldiag[n - row + i - 1] or rdiag[row + i]: continue # 修改当前节点状态。 board[row][i] = "Q" column[i] = ldiag[n - row + i - 1] = rdiag[row + i] = True # 递归子节点。 backtracking(solutions, board, column, ldiag, rdiag, row + 1) # 回改当前节点状态。 board[row][i] = "." column[i] = ldiag[n - row + i - 1] = rdiag[row + i] = False # 主函数。 def solveNQueens(n: int) -> List[List[str]]: solutions = [] board = [["." for _ in range(n)] for _ in range(n)] column = [False] * n ldiag = [False] * (2 * n - 1) rdiag = [False] * (2 * n - 1) backtracking(solutions, board, column, ldiag, rdiag, 0) return solutions ```
嘉心糖糖
2025年9月30日 19:49
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
PDF文档(打印)
分享
链接
类型
密码
更新密码