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.4 广度优先搜索
# 5.4 广度优先搜索 `广度优先搜索`(breadth-first search,BFS)不同与深度优先搜索,它是一层层进行遍历的,因此`需要用先入先出的队列 (queue)` 而非先入后出的栈 (stack) 进行遍历。由于是按层次进行遍历,广度优先搜索时按照“广”的方向进行遍历的,也常常用来处理最短路径等问题。在 Python 中,我们可以用 collections.deque 来实现 C++ 中的 queue。 ``` 1 / \ 2 3 / 4 ``` 这里要注意,深度优先搜索和广度优先搜索都可以处理`可达性`问题,即从一个节点开始是否能达到另一个节点。因为深度优先搜索可以利用递归快速实现,很多人会习惯使用深度优先搜索刷此类题目。实际软件工程中,笔者很少见到递归的写法,因为一方面难以理解,另一方面可能产生栈溢出的情况;而用栈实现的深度优先搜索和用队列实现的广度优先搜索在写法上并没有太大差异,因此使用哪一种搜索方式需要根据实际的功能需求来判断。另外,如果需要自定义搜索优先级,我们可以利用优先队列,这个我们会在数据结构的章节讲到。 ## [1091. Shortest Path in Binary Matrix](https://leetcode.com/problems/shortest-path-in-binary-matrix/) ### 题目描述 给定一个二维 0-1 矩阵,其中 1 表示障碍,0 表示道路,每个位置与周围八个格子相连。求从左上角到右下角的最短到达距离。如果没有可以到达的方法,返回-1。 ### 输入输出样例 输入是一个二维整数数组,输出是一个整数,表示最短距离。 ``` Input: [[0,0,1], [1,1,0], [1,1,0]] Output: 4 ``` 最短到达方法为先向右,拐弯之后再向下。 ### 题解 利用队列,我们可以很直观地利用广度优先搜索,搜索最少扩展层数,即最短到达目的地的距离。注意不要重复搜索相同位置。 ```py def shortestPathBinaryMatrix(grid: List[List[int]]) -> int: if grid[0][0] == 1: return -1 m, n = len(grid), len(grid[0]) dist = 0 q = collections.deque() q.append((0, 0)) grid[0][0] = -1 # -1表示visited count = len(q) while count > 0: dist += 1 while count > 0: count -= 1 r, c = q.popleft() if r == m - 1 and c == n - 1: return dist for dx in range(-1, 2): for dy in range(-1, 2): if dx == 0 and dy == 0: continue x, y = r + dx, c + dy if x < 0 or y < 0 or x >= m or y >= n or grid[x][y] != 0: continue grid[x][y] = -1 q.append((x, y)) count = len(q) return -1 ``` ## 934. 最短的桥(中等) [934. Shortest Bridge](https://leetcode.cn/problems/shortest-bridge/description/) ### 题目描述 给定一个二维 0-1 矩阵,其中 1 表示陆地,0 表示海洋,每个位置与上下左右相连。已知矩阵中有且只有两个岛屿,求最少要填海造陆多少个位置才可以将两个岛屿相连。 ### 输入输出样例 输入是一个二维整数数组,输出是一个非负整数,表示需要填海造陆的位置数。 ``` Input: [[1,1,1,1,1], [1,0,0,0,1], [1,0,1,0,1], [1,0,0,0,1], [1,1,1,1,1]] Output: 1 ``` ### 题解 本题实际上是求两个岛屿间的最短距离,因此我们可以先通过任意搜索方法找到其中一个岛屿,然后利用广度优先搜索,查找其与另一个岛屿的最短距离。这里我们展示利用深度优先搜索查找第一个岛屿。 ```py class Solution: direction = [-1, 0, 1, 0, -1] def dfs(self, grid, water, i, j): if not (0 <= i < len(grid) and 0 <= j < len(grid[0])) or grid[i][j] == 2: return if grid[i][j] == 0: water.append((i,j)) return grid[i][j] = 2 for k in range(4): x = i + self.direction[k] y = j + self.direction[k+1] self.dfs(grid, water, x, y) def shortestBridge(self, grid: List[List[int]]) -> int: m = len(grid) n = len(grid[0]) ifind = False water = collections.deque() level = 0 for i in range(m): if ifind: break for j in range(n): if grid[i][j] == 1: self.dfs(grid, water, i, j) ifind = True break while water: level += 1 size = len(water) for _ in range(size): r, c = water.popleft() grid[r][c] = 2 for k in range(4): x = r + self.direction[k] y = c + self.direction[k + 1] if 0 <= x < m and 0 <= y < n: if grid[x][y] == 2: continue if grid[x][y] == 1: return level grid[x][y] = 2 water.append((x,y)) return level ``` ## 126. 单词接龙 II(困难) [126. Word Ladder II](https://leetcode.cn/problems/word-ladder-ii/description/) ### 题目描述 给定一个起始字符串和一个终止字符串,以及一个单词表,求是否可以将起始字符串每次改一个字符,直到改成终止字符串,且所有中间的修改过程表示的字符串都可以在单词表里找到。若存在,输出需要修改次数最少的所有更改方式。 ### 输入输出样例 输入是两个字符串,输出是一个二维字符串数组,表示每种字符串修改方式。 ``` Input: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"] Output: [["hit","hot","dot","dog","cog"], ["hit","hot","lot","log","cog"]] ``` ### 题解 我们可以把起始字符串、终止字符串、以及单词表里所有的字符串想象成节点。若两个字符串只有一个字符不同,那么它们相连。因为题目需要输出修改次数最少的所有修改方式,因此我们可以使用广度优先搜索,求得起始节点到终止节点的最短距离。 我们同时还使用了一个小技巧:我们并不是直接从起始节点进行广度优先搜索,直到找到终止节点为止;而是从起始节点和终止节点分别进行广度优先搜索,每次只延展当前层节点数最少的那一端,这样我们可以减少搜索的总结点数。举例来说,假设最短距离为 4,如果我们只从一端搜索 4 层,总遍历节点数最多是 $1 + 2 + 4 + 8 + 16 = 31$;而如果我们从两端各搜索两层,总遍历节点数最多只有 $2 × (1 + 2 + 4) =14$。 在搜索结束后,我们还需要通过回溯法来重建所有可能的路径。 这道题略微复杂,需要读者耐心思考和实现代码。LeetCode 对于本题的时间要求非常严格,即使是官方题解也经常容易超时,可以尝试多次提交。 ```py # 辅函数。 def backtracking(src: str, dst: str, next_words: Dict[str, List[str]], path: List[str], ladder: List[List[str]]): if src == dst: ladder.append(path[:]) return if src not in next_words: return for w in next_words[src]: path.append(w) # 修改当前节点状态 backtracking(w, dst, next_words, path, ladder) # 递归子节点 path.pop() # 回改当前节点状态 # 主函数。 def findLadders(beginWord: str, endWord: str, wordList: List[str]) -> List[List[str]]: ladder = [] # 用哈希集合存储字典,方便查找。 word_dict = set(wordList) if endWord not in word_dict: return ladder word_dict = word_dict.difference(set([beginWord, endWord])) # 建立两个queue,从beginWord和endWord同时延展,每次延展最小的。 # 因为之后的去重操作需要遍历queue,我们这里用哈希表实现它, # 只要保证是分层次遍历即可。 q_small, q_large = set([beginWord]), set([endWord]) next_words = dict() reversed_path, found_path = False, False while len(q_small) > 0: q = set() for w in q_small: for i in range(len(w)): for j in range(26): s = w[:i] + chr(ord("a") + j) + w[i + 1:] if s in q_large: if reversed_path: next_words[s] = next_words.get(s, []) + [w] else: next_words[w] = next_words.get(w, []) + [s] found_path = True if s in word_dict: if reversed_path: next_words[s] = next_words.get(s, []) + [w] else: next_words[w] = next_words.get(w, []) + [s] q.add(s) if found_path: break # 环路一定不是最短解,所以这里需要去重和避免无限循环。 word_dict = word_dict.difference(q) # 更新两个queue,并维持大小关系。 if len(q) <= len(q_large): q_small = q else: reversed_path = not reversed_path q_small = q_large q_large = q if found_path: path = [beginWord] backtracking(beginWord, endWord, next_words, path, ladder) return ladder ```
嘉心糖糖
2025年3月11日 19:07
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
PDF文档(打印)
分享
链接
类型
密码
更新密码