210. 课程表 II(Course Schedule II)

难度:中等 | 主题:图论、拓扑排序、BFS、DFS

题目

现在你总共有 numCourses 门课需要选,记为 0 到 numCourses-1。在选修某些课程之前需要一些先修课程。给定课程总量以及它们的先决条件,返回你为了学完所有课程所安排的学习顺序。

示例

 
输入:numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]
 
输出:[0,1,2,3] 或 [0,2,1,3]
 

思路

先讲个故事:选课系统的最优顺序

教务系统不仅要知道能不能选完,还要给出最优选课顺序

Kahn 算法天然就能输出拓扑序——每次处理入度为 0 的节点时,就把它加入结果列表。


引导式推导:207 的一行升级

与 207 题的关系: 207 只判断是否有环,210 还要输出拓扑序。


flowchart TD

    A["建图 + 入度数组"] --> B["入度为 0 的入队"]

    B --> C["出队 → 加入 result"]

    C --> D["后继入度 -1"]

    D --> E{"入度变 0?"}

    E -->|"是"| F["入队"]

    E -->|"否"| G["继续"]

    F --> H{"队列空?"}

    G --> H

    H -->|"否"| C

    H -->|"是"| I["result 长度 == numCourses?"]

    I -->|"是"| J["返回 result"]

    I -->|"否"| K["有环 → 返回 []"]

关键洞察: BFS 拓扑排序(Kahn 算法)天然可以输出拓扑序——每次处理入度为 0 的节点时,就把它加入结果列表。


代码

 
from collections import deque, defaultdict
 
class Solution:
 
    def findOrder(self, numCourses: int, prerequisites: list[list[int]]) -> list[int]:
 
        graph = defaultdict(list)
 
        indegree = [0] * numCourses
 
        for a, b in prerequisites:
 
            graph[b].append(a)
 
            indegree[a] += 1
 
        queue = deque()
 
        for i in range(numCourses):
 
            if indegree[i] == 0:
 
                queue.append(i)
 
        result = []
 
        while queue:
 
            course = queue.popleft()
 
            result.append(course)
 
            for next_course in graph[course]:
 
                indegree[next_course] -= 1
 
                if indegree[next_course] == 0:
 
                    queue.append(next_course)
 
        return result if len(result) == numCourses else []
 

复杂度

指标解释
时间O(V+E)建图 + 拓扑排序
空间O(V+E)图 + 入度数组 + 结果

实战考量

频率分析

出现在:字节/阿里 高频题,约 30% 常会考到。207 的进阶版,考察拓扑序输出

延伸思考

Q:拓扑序唯一吗?

A:不唯一,入度为 0 的节点可能有多个,选哪个都行。

Q:怎么保证输出字典序最小的拓扑序?

A:用最小堆(优先队列)代替普通队列,每次选编号最小的。

Q:如果要求并行上课(每学期尽可能多的课)呢?

A:按层输出拓扑序,每层是可以同时上的课。

Q:如果要求输出所有可能的拓扑序呢?

A:DFS + 回溯,每次选一个入度为 0 的节点,递归处理。

易错点

  • 最后检查 len(result) == numCourses

  • 有环时返回空列表不是 None

  • 拓扑序不唯一,题目只要求返回任意一个


生活类比

选课系统的最优顺序

教务系统不仅想知道能不能选完,还要给出一个选课清单。

Kahn 算法每次选一门没有先修要求的课(入度为 0),加入清单。

选完后,依赖它的课的先修数 -1,如果变成 0 就可以选了。

最后清单就是拓扑序——按这个顺序选课,保证先修课在前面。

入度为 0 = 可以选 = 加入清单——这就是拓扑排序输出顺序。


相关题目

题目关系
207课程表只判环,不输出顺序
210课程表II本题
684冗余连接无向图判环

→ 返回题单:LeetCode学习路线图 > 十二、图论

速记卡(面试闪卡)

Q1:一句话讲清「210. 课程表 II(Course Schedule II)」到底是什么?

A:给定先修关系,输出能学完所有课程的拓扑顺序。

Q2:思路 —— 怎么理解?

A:像教务排课:每次选一门没有先修要求的课(入度为 0)加入清单,它解放的后继先修数 -1。这就是 Topological Sort(拓扑排序)/ Kahn 算法。

Q3:代码 —— 怎么理解?

A:建图+入度数组,入度 0 入队;出队加入 result,后继入度 -1,变 0 再入队。最后 result 长度==numCourses 才有效,否则有环返 []。

Q4:复杂度 —— 怎么理解?

A:时间 O(V+E)(建图+排序),空间 O(V+E)(图+入度+结果)。BFS 天然输出拓扑序。

Q5:实战考量 —— 怎么理解?

A:字节/阿里高频,207 的进阶(207 只判环)。拓扑序不唯一;要字典序最小用最小堆;有环返 [] 不是 None。

Q6:核心速记主线有哪些?

  • Kahn 算法:入度 0 入队,处理完后继入度 -1

  • BFS 天然输出拓扑序,每次出队加入结果

  • 时间 O(V+E)、空间 O(V+E)

  • 易错:检查 len==numCourses,有环返 [] 非 None

口诀

A:选课排顺序

入度零先去

后继减一数

拓扑序清晰

相关链接