LeetCode P210 课程表 II
核心思想
此问题本质上是在一个有向图中寻找一个拓扑排序。每门课程是一个图的节点,课程间的先修关系 [a, b] (想学 a 必须先学 b) 可以看作一条从 b 到 a 的有向边 b -> a。
我们的目标是输出一个所有节点的线性序列,使得对于任意边 b -> a,节点 b 都在节点 a 之前。如果图中存在环(例如,A依赖B,B依赖A),则说明无法完成所有课程,不存在拓扑排序。
本题解采用深度优先搜索 (DFS) 来实现拓扑排序和环检测。
算法思路
图的构建
使用邻接表 adj 来表示图。adj[i] 存储所有以课程 i 为先修课的课程列表。例如,对于 [a, b],我们将 a 添加到 adj[b] 中。
节点状态
为了在 DFS 中检测环并构建拓扑序列,我们为每个节点(课程)维护一个状态 visited:
0(未访问):初始状态,表示该节点还未被访问。1(访问中):表示该节点已经在此次 DFS 的递归路径上,但其所有邻接点尚未完全探索。2(已访问):表示该节点及其所有邻接点都已被完全访问。
DFS 过程
从一个未访问的节点开始进行 DFS。首先将其状态标记为 1 (访问中)。遍历其所有邻接点:
- 如果邻接点是
0(未访问),则对其进行递归 DFS。 - 如果邻接点是
1(访问中),说明我们从当前节点出发,又回到了同一条递归路径上的某个节点,这表明图中存在环。立即停止并标记有环。 - 如果邻接点是
2(已访问),说明它已被安全访问过,无需处理。
当一个节点的所有邻接点都访问完毕后,将其状态标记为 2 (已访问),并将其追加到结果列表 result 的末尾。
结果生成
遍历所有课程,对每个状态为 0 的课程调用 DFS。如果在任何一次 DFS 中检测到环,直接返回空数组 []。
由于 DFS 是在访问完所有后续课程后才将当前课程加入 result,所以 result 中课程的顺序是拓扑排序的逆序。因此,最终返回 result 的反转列表 result[::-1]。
代码解析
from typing import List
class Solution:
def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
# 1. 初始化邻接表、访问状态数组和结果列表
adj = [[] for _ in range(numCourses)]
# 构建邻接表: b -> a
for course, prereq in prerequisites:
adj[prereq].append(course)
visited = [0] * numCourses # 0: 未访问, 1: 访问中, 2: 已访问
result = []
has_cycle = False
def dfs(course: int):
nonlocal has_cycle
# 标记为访问中
visited[course] = 1
for neighbor in adj[course]:
if visited[neighbor] == 0:
dfs(neighbor)
if has_cycle: return # 如果下游检测到环,提前返回
elif visited[neighbor] == 1:
# 发现环,直接标记并返回
has_cycle = True
return
# 3. 所有邻接点访问完毕,标记为已访问,并加入结果列表
visited[course] = 2
result.append(course)
# 4. 遍历所有课程,确保处理图中所有连通分量
for i in range(numCourses):
if visited[i] == 0:
dfs(i)
if has_cycle:
return [] # 检测到环,无法完成课程
# 5. DFS 得到的是逆后序遍历,反转即为拓扑排序
return result[::-1]

浙公网安备 33010602011771号