Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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)

AltStyle によって変換されたページ (->オリジナル) /