Skip to content

Commit 46c2a10

Browse files
committed
更新分类题解列表
1 parent 4688815 commit 46c2a10

File tree

4 files changed

+303
-8
lines changed

4 files changed

+303
-8
lines changed

Assets/Origins/Categories-List.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@
201201

202202
### [图的拓扑排序题目](../../Contents/08.Graph/02.Graph-Traversal/06.Graph-Topological-Sorting-List.md)
203203

204-
###### 0210. 课程表 II、0802. 找到最终的安全状态、0851. 喧闹和富有
204+
###### 0207. 课程表、0210. 课程表 II、0802. 找到最终的安全状态、0851. 喧闹和富有
205205

206206
### [图的生成树题目](../../Contents/08.Graph/03.Gaph-Spanning-Tree/04.Gaph-Spanning-Tree-List.md)
207207

Contents/00.Introduction/05.Categories-List.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,7 @@
642642

643643
| 题号 | 标题 | 题解 | 标签 | 难度 |
644644
| :------ | :------ | :------ | :------ | :------ |
645+
| 0207 | [课程表](https://leetcode.cn/problems/course-schedule/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0207.%20%E8%AF%BE%E7%A8%8B%E8%A1%A8.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 |
645646
| 0210 | [课程表 II](https://leetcode.cn/problems/course-schedule-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0210.%20%E8%AF%BE%E7%A8%8B%E8%A1%A8%20II.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 |
646647
| 0802 | [找到最终的安全状态](https://leetcode.cn/problems/find-eventual-safe-states/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0802.%20%E6%89%BE%E5%88%B0%E6%9C%80%E7%BB%88%E7%9A%84%E5%AE%89%E5%85%A8%E7%8A%B6%E6%80%81.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 |
647648
| 0851 | [喧闹和富有](https://leetcode.cn/problems/loud-and-rich/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0851.%20%E5%96%A7%E9%97%B9%E5%92%8C%E5%AF%8C%E6%9C%89.md) | 深度优先搜索、图、拓扑排序、数组 | 中等 |

Contents/08.Graph/02.Graph-Traversal/05.Graph-Topological-Sorting.md

Lines changed: 300 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,41 +10,334 @@
1010

1111
## 2. 拓扑排序的实现方法
1212

13-
拓扑排序有两种实现方法,分别是 Kahn 算法和 DFS 深度优先搜索算法。接下来我们依次来看下它们是如何实现的。
13+
拓扑排序有两种实现方法,分别是Kahn 算法」和「DFS 深度优先搜索算法。接下来我们依次来看下它们是如何实现的。
1414

1515
### 2.1 Kahn 算法
1616

17+
> **Kahn 算法的基本思想**
18+
1719
#### 2.1.1 Kahn 算法的实现步骤
1820

1921
1. 使用数组 $indegrees$ 用于记录图中各个顶点的入度。
2022
2. 维护一个入度为 $0$ 的顶点集合 $S$(可使用栈、队列、优先队列)。
2123
3. 每次从集合中选择任何一个没有前驱(即入度为 $0$)的顶点 $u$,将其输出到拓扑序列 $order$ 中。
2224
4. 从图中删除该顶点 $u$,并且删除从该顶点出发的有向边 $<u, v>$(也就是把该顶点可达的顶点入度都减 $1$)。如果删除该边后顶点 $v$ 的入度变为 $0$,则将顶点 $v$ 放入集合 $S$ 中。
23-
5. 重复上述过程,直到集合 $S$ 为空。
24-
6. 检测图中是否存在任何边,如果有,则该图一定存在环路。否则 $order$ 中顶点的顺序就是拓扑排序的结果。
25+
5. 重复上述过程,直到集合 $S$ 为空,或者图中还有顶点未被访问(说明一定存在环路,无法形成拓扑序列)
26+
6. 如果不存在环路,则 $order$ 中顶点的顺序就是拓扑排序的结果。
2527

2628
#### 2.1.2 Kahn 算法的实现代码
2729

2830
```Python
31+
import collections
2932

33+
class Solution:
34+
# 拓扑排序,graph 中包含所有顶点的有向边关系(包括无边顶点)
35+
def topologicalSortingKahn(self, graph: dict):
36+
indegrees = {u: 0 for u in graph} # indegrees 用于记录所有顶点入度
37+
for u in graph:
38+
for v in graph[u]:
39+
indegrees[v] += 1 # 统计所有顶点入度
40+
41+
# 将入度为 0 的顶点存入集合 S 中
42+
S = collections.deque([u for u in indegrees if indegrees[u] == 0])
43+
order = [] # order 用于存储拓扑序列
44+
45+
while S:
46+
u = S.pop() # 从集合中选择一个没有前驱的顶点 0
47+
order.append(u) # 将其输出到拓扑序列 order 中
48+
for v in graph[u]: # 遍历顶点 u 的邻接顶点 v
49+
indegrees[v] -= 1 # 删除从顶点 u 出发的有向边
50+
if indegrees[v] == 0: # 如果删除该边后顶点 v 的入度变为 0
51+
S.append(v) # 将其放入集合 S 中
52+
53+
if len(indegrees) != len(order): # 还有顶点未遍历(存在环),无法构成拓扑序列
54+
return []
55+
return order # 返回拓扑序列
56+
57+
58+
def findOrder(self, n: int, edges):
59+
# 构建图
60+
graph = dict()
61+
for i in range(n):
62+
graph[i] = []
63+
64+
for u, v in edges:
65+
graph[u].append(v)
66+
67+
return self.topologicalSortingKahn(graph)
3068
```
3169

32-
### 2.2 DFS 深度优先搜索算法
70+
### 2.2 基于 DFS 实现拓扑排序算法
71+
72+
> **基于 DFS 实现拓扑排序算法的基本思想**
73+
>
74+
> 1. 对于一个顶点 $u$,深度游先生遍历从该顶点出发的有向边 $<u, v>$。如果从该顶点 $u$ 出发的所有相邻顶点 $v$ 都已经搜索完毕,则在搜索回溯到顶点 $u$ 时,$u$ 本身也会编程一个已经搜索完的顶点。
75+
> 2. 在拓扑排序的序列中,该顶点 $u$ 位于其所有相邻顶点 $v$ 的前面。
76+
> 3. 这样一来,我们对每个顶点进行回溯时,将其放入栈中,则最终从栈顶到栈底的序列就是一种拓扑排序。
3377
34-
#### 2.2.1 DFS 深度优先搜索算法实现步骤
78+
#### 2.2.1 基于 DFS 实现拓扑排序算法实现步骤
3579

36-
1. 以任意顺序循环遍历图中的每个顶点,将其输出到拓扑序列中。
37-
2. 如果搜索时遇到之前已经遇到的顶点,或者碰到叶节点,则中止算法。
80+
1. 使用集合 $visited$ 用于记录当前顶点是否被访问过,避免重复访问。
81+
2. 使用集合 $onStack$ 用于记录同一次深度优先搜索时,当前顶点是否被访问过。如果当前顶点被访问过,则说明图中存在环路,无法构成拓扑序列。
82+
3. 使用布尔变量 $hasCycle$ 用于判断图中是否存在环。
83+
4. 从任意一个未被访问的顶点 $u$ 出发。
84+
1. 如果顶点 $u$ 在同一次深度优先搜索时被访问过,则说明存在环。
85+
2. 如果当前顶点被访问或者有环时,则无需再继续遍历,直接返回。
86+
87+
5. 将顶点 $u$ 标记为被访问过,并在本次深度优先搜索中标记为访问过。然后深度游先生遍历从顶点 $u$ 出发的有向边 $<u, v>$。
88+
6. 当顶点 $u$ 的所有相邻顶点 $v$ 都被访问后,回溯前记录当前节点 $u$(将当前节点 $u$ 输出到拓扑序列 $order$ 中)。
89+
7. 取消本次深度优先搜索时,顶点 $u$ 的访问标记。
90+
8. 对其他未被访问的顶点重复 $4 \sim 7$ 步过程,直到所有节点都遍历完,或者出现环。
91+
9. 如果不存在环路,则将 $order$ 逆序排序后,顶点的顺序就是拓扑排序的结果。
3892

3993
#### 2.2.2 DFS 深度优先搜索算法实现代码
4094

4195
```Python
96+
import collections
4297

98+
class Solution:
99+
# 拓扑排序,graph 中包含所有顶点的有向边关系(包括无边顶点)
100+
def topologicalSortingDFS(self, graph: dict):
101+
visited = set() # 记录当前顶点是否被访问过
102+
onStack = set() # 记录同一次深搜时,当前顶点是否被访问过
103+
order = [] # 用于存储拓扑序列
104+
hasCycle = False # 用于判断是否存在环
105+
106+
def dfs(u):
107+
nonlocal hasCycle
108+
if u in onStack: # 同一次深度优先搜索时,当前顶点被访问过,说明存在环
109+
hasCycle = True
110+
if u in visited or hasCycle: # 当前节点被访问或者有环时直接返回
111+
return
112+
113+
visited.add(u) # 标记节点被访问
114+
onStack.add(u) # 标记本次深搜时,当前顶点被访问
115+
116+
for v in graph[u]: # 遍历顶点 u 的邻接顶点 v
117+
dfs(v) # 递归访问节点 v
118+
119+
order.append(u) # 后序遍历顺序访问节点 u
120+
onStack.remove(u) # 取消本次深搜时的 顶点访问标记
121+
122+
for u in graph:
123+
if u not in visited:
124+
dfs(u) # 递归遍历未访问节点 u
125+
126+
if hasCycle: # 判断是否存在环
127+
return [] # 存在环,无法构成拓扑序列
128+
order.reverse() # 将后序遍历转为拓扑排序顺序
129+
return order # 返回拓扑序列
130+
131+
def findOrder(self, n: int, edges):
132+
# 构建图
133+
graph = dict()
134+
for i in range(n):
135+
graph[i] = []
136+
for v, u in edges:
137+
graph[u].append(v)
138+
139+
return self.topologicalSortingDFS(graph)
43140
```
44141

45142
## 3. 拓扑排序的应用
46143

47144
拓扑排序可以用来解决一些依赖关系的问题,比如项目的执行顺序,课程的选修顺序等。
48145

146+
### 3.1 课程表 II
147+
148+
#### 3.1.1 题目链接
149+
150+
- [210. 课程表 II - 力扣](https://leetcode.cn/problems/course-schedule-ii/)
151+
152+
#### 3.1.2 题目大意
153+
154+
**描述**:给定一个整数 $numCourses$,代表这学期必须选修的课程数量,课程编号为 $0 \sim numCourses - 1$。再给定一个数组 $prerequisites$ 表示先修课程关系,其中 $prerequisites[i] = [ai, bi]$ 表示如果要学习课程 $ai$ 则必须要学习课程 $bi$。
155+
156+
**要求**:返回学完所有课程所安排的学习顺序。如果有多个正确的顺序,只要返回其中一种即可。如果无法完成所有课程,则返回空数组。
157+
158+
**说明**
159+
160+
- $1 \le numCourses \le 2000$。
161+
- $0 \le prerequisites.length \le numCourses \times (numCourses - 1)$。
162+
- $prerequisites[i].length == 2$。
163+
- $0 \le ai, bi < numCourses$。
164+
- $ai \ne bi$。
165+
- 所有$[ai, bi]$ 互不相同。
166+
167+
**示例**
168+
169+
- 示例 1:
170+
171+
```Python
172+
输入:numCourses = 2, prerequisites = [[1,0]]
173+
输出:[0,1]
174+
解释:总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1]。
175+
```
176+
177+
- 示例 2:
178+
179+
```Python
180+
输入:numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]
181+
输出:[0,2,1,3]
182+
解释:总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。
183+
因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3]。
184+
```
185+
186+
#### 3.1.3 解题思路
187+
188+
##### 思路 1:拓扑排序
189+
190+
这道题是「[0207. 课程表](https://leetcode.cn/problems/course-schedule/)」的升级版,只需要在上一题的基础上增加一个答案数组 $order$ 即可。
191+
192+
1. 使用哈希表 $graph$ 存放课程关系图,并统计每门课程节点的入度,存入入度列表 $indegrees$。
193+
2. 借助队列 $S$,将所有入度为 $0$ 的节点入队。
194+
3. 从队列中选择一个节点 $u$,并将其加入到答案数组 $order$ 中。
195+
4. 从图中删除该顶点 $u$,并且删除从该顶点出发的有向边 $<u, v>$(也就是把该顶点可达的顶点入度都减 $1$)。如果删除该边后顶点 $v$ 的入度变为 $0$,则将其加入队列 $S$ 中。
196+
5. 重复上述步骤 $3 \sim 4$,直到队列中没有节点。
197+
6. 最后判断总的顶点数和拓扑序列中的顶点数是否相等,如果相等,则返回答案数组 $order$,否则,返回空数组。
198+
199+
##### 思路 1:代码
200+
201+
```Python
202+
import collections
203+
204+
class Solution:
205+
# 拓扑排序,graph 中包含所有顶点的有向边关系(包括无边顶点)
206+
def topologicalSortingKahn(self, graph: dict):
207+
indegrees = {u: 0 for u in graph} # indegrees 用于记录所有顶点入度
208+
for u in graph:
209+
for v in graph[u]:
210+
indegrees[v] += 1 # 统计所有顶点入度
211+
212+
# 将入度为 0 的顶点存入集合 S 中
213+
S = collections.deque([u for u in indegrees if indegrees[u] == 0])
214+
order = [] # order 用于存储拓扑序列
215+
216+
while S:
217+
u = S.pop() # 从集合中选择一个没有前驱的顶点 0
218+
order.append(u) # 将其输出到拓扑序列 order 中
219+
for v in graph[u]: # 遍历顶点 u 的邻接顶点 v
220+
indegrees[v] -= 1 # 删除从顶点 u 出发的有向边
221+
if indegrees[v] == 0: # 如果删除该边后顶点 v 的入度变为 0
222+
S.append(v) # 将其放入集合 S 中
223+
224+
if len(indegrees) != len(order): # 还有顶点未遍历(存在环),无法构成拓扑序列
225+
return []
226+
return order # 返回拓扑序列
227+
228+
229+
def findOrder(self, numCourses: int, prerequisites):
230+
graph = dict()
231+
for i in range(numCourses):
232+
graph[i] = []
233+
234+
for v, u in prerequisites:
235+
graph[u].append(v)
236+
237+
return self.topologicalSortingKahn(graph)
238+
```
239+
240+
##### 思路 1:复杂度分析
241+
242+
- **时间复杂度**:$O(n + m)$,其中 $n$ 为课程数,$m$ 为先修课程的要求数。
243+
- **空间复杂度**:$O(n + m)$。
244+
245+
### 3.2 找到最终的安全状态
246+
247+
#### 3.2.1 题目链接
248+
249+
- [802. 找到最终的安全状态 - 力扣](https://leetcode.cn/problems/find-eventual-safe-states/)
250+
251+
#### 3.2.2 题目大意
252+
253+
**描述**:给定一个有向图 $graph$,其中 $graph[i]$ 是与节点 $i$ 相邻的节点列表,意味着从节点 $i$ 到节点 $graph[i]$ 中的每个节点都有一条有向边。
254+
255+
**要求**:找出图中所有的安全节点,将其存入数组作为答案返回,答案数组中的元素应当按升序排列。
256+
257+
**说明**
258+
259+
- **终端节点**:如果一个节点没有连出的有向边,则它是终端节点。或者说,如果没有出边,则节点为终端节点。
260+
- **安全节点**:如果从该节点开始的所有可能路径都通向终端节点,则该节点为安全节点。
261+
- $n == graph.length$。
262+
- $1 \le n \le 10^4$。
263+
- $0 \le graph[i].length \le n$。
264+
- $0 \le graph[i][j] \le n - 1$。
265+
- $graph[i]$ 按严格递增顺序排列。
266+
- 图中可能包含自环。
267+
- 图中边的数目在范围 $[1, 4 \times 10^4]$ 内。
268+
269+
**示例**
270+
271+
- 示例 1:
272+
273+
![](https://s3-lc-upload.s3.amazonaws.com/uploads/2018/03/17/picture1.png)
274+
275+
```Python
276+
输入:graph = [[1,2],[2,3],[5],[0],[5],[],[]]
277+
输出:[2,4,5,6]
278+
解释:示意图如上。
279+
节点 5 和节点 6 是终端节点,因为它们都没有出边。
280+
从节点 2456 开始的所有路径都指向节点 56
281+
```
282+
283+
- 示例 2:
284+
285+
```Python
286+
输入:graph = [[1,2,3,4],[1,2],[3,4],[0,4],[]]
287+
输出:[4]
288+
解释:
289+
只有节点 4 是终端节点,从节点 4 开始的所有路径都通向节点 4
290+
```
291+
292+
#### 3.2.3 解题思路
293+
294+
##### 思路 1:拓扑排序
295+
296+
1. 根据题意可知,安全节点所对应的终点,一定是出度为 $0$ 的节点。而安全节点一定能在有限步内到达终点,则说明安全节点一定不在「环」内。
297+
2. 我们可以利用拓扑排序来判断顶点是否在环中。
298+
3. 为了找出安全节点,可以采取逆序建图的方式,将所有边进行反向。这样出度为 $0$ 的终点就变为了入度为 $0$ 的点。
299+
4. 然后通过拓扑排序不断移除入度为 $0$ 的点之后,如果不在「环」中的点,最后入度一定为 $0$,这些点也就是安全节点。而在「环」中的点,最后入度一定不为 $0$。
300+
5. 最后将所有安全的起始节点存入数组作为答案返回。
301+
302+
##### 思路 1:代码
303+
304+
```Python
305+
class Solution:
306+
# 拓扑排序,graph 中包含所有顶点的有向边关系(包括无边顶点)
307+
def topologicalSortingKahn(self, graph: dict):
308+
indegrees = {u: 0 for u in graph} # indegrees 用于记录所有节点入度
309+
for u in graph:
310+
for v in graph[u]:
311+
indegrees[v] += 1 # 统计所有节点入度
312+
313+
# 将入度为 0 的顶点存入集合 S 中
314+
S = collections.deque([u for u in indegrees if indegrees[u] == 0])
315+
316+
while S:
317+
u = S.pop() # 从集合中选择一个没有前驱的顶点 0
318+
for v in graph[u]: # 遍历顶点 u 的邻接顶点 v
319+
indegrees[v] -= 1 # 删除从顶点 u 出发的有向边
320+
if indegrees[v] == 0: # 如果删除该边后顶点 v 的入度变为 0
321+
S.append(v) # 将其放入集合 S 中
322+
323+
res = []
324+
for u in indegrees:
325+
if indegrees[u] == 0:
326+
res.append(u)
327+
328+
return res
329+
330+
def eventualSafeNodes(self, graph: List[List[int]]) -> List[int]:
331+
graph_dict = {u: [] for u in range(len(graph))}
332+
333+
for u in range(len(graph)):
334+
for v in graph[u]:
335+
graph_dict[v].append(u) # 逆序建图
336+
337+
return self.topologicalSortingKahn(graph_dict)
338+
```
49339

340+
##### 思路 1:复杂度分析
50341

342+
- **时间复杂度**:$O(n + m)$,其中 $n$ 是图中节点数目,$m$ 是图中边数目。
343+
- **空间复杂度**:$O(n + m)$。

Contents/08.Graph/02.Graph-Traversal/06.Graph-Topological-Sorting-List.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
| 题号 | 标题 | 题解 | 标签 | 难度 |
44
| :------ | :------ | :------ | :------ | :------ |
5+
| 0207 | [课程表](https://leetcode.cn/problems/course-schedule/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0207.%20%E8%AF%BE%E7%A8%8B%E8%A1%A8.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 |
56
| 0210 | [课程表 II](https://leetcode.cn/problems/course-schedule-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0210.%20%E8%AF%BE%E7%A8%8B%E8%A1%A8%20II.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 |
67
| 0802 | [找到最终的安全状态](https://leetcode.cn/problems/find-eventual-safe-states/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0802.%20%E6%89%BE%E5%88%B0%E6%9C%80%E7%BB%88%E7%9A%84%E5%AE%89%E5%85%A8%E7%8A%B6%E6%80%81.md) | 深度优先搜索、广度优先搜索、图、拓扑排序 | 中等 |
78
| 0851 | [喧闹和富有](https://leetcode.cn/problems/loud-and-rich/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0851.%20%E5%96%A7%E9%97%B9%E5%92%8C%E5%AF%8C%E6%9C%89.md) | 深度优先搜索、图、拓扑排序、数组 | 中等 |

0 commit comments

Comments
 (0)