课程表
Course Schedule
给出 n 门课和「先修某课才能修另一课」的限制,判断能否排出一个修完全部课程的顺序。
在力扣中练习题目描述
你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。
- 例如,先修课程对
[0, 1]表示:想要学习课程0,你需要先完成课程1。
请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。
示例 1:
输入:numCourses = 2, prerequisites = [[1,0]] 输出:true 解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。
示例 2:
输入:numCourses = 2, prerequisites = [[1,0],[0,1]] 输出:false 解释:总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0 ;并且学习课程 0 之前,你还应先完成课程 1 。这是不可能的。
提示:
1 <= numCourses <= 20000 <= prerequisites.length <= 5000prerequisites[i].length == 20 <= ai, bi < numCoursesprerequisites[i]中的所有课程对 互不相同
解题思路
拓扑排序 · Kahn(BFS)
反复修掉入度为 0 的课,看最后能不能全部修完
逐条看先修关系很难判断能否全部完成。把先修课指向后续课,并记录每门课还有多少先修未完成;剩余入度为 0 的课,才允许进入队列。
自拟主例共 6 门课,依赖为 [[1,0],[2,0],[3,1],[4,3],[3,4]]。课程 5 独立,0 分叉到 1 和 2,3 与 4 互相等待。
建图并统计入度;课程 5 独立,3 与 4 相互依赖。
取出一门可修课程,就减少它指向的每门后续课程的入度。只有减到 0 才入队;仍缺一门先修时不能提前安排。所有课程都要建立节点,独立课程也不能遗漏。
修完 1,更新它指向的课程;已修 3/6 门。
- taken
- 3
本例能修完 0、5、1、2,但 3、4 始终有未满足依赖。队列为空仅表示当前无课可修,还必须比较 taken 是否等于总课程数;此处 4 不等于 6,返回 false。
易错点
- 边建反了:prerequisites[i] = [a, b] 是 b 先修,边必须是 b → a,入度加在 a 上。
- 只用「队列是否为空」判结果,不记 taken —— 有环时队列也会空,但课没修完。
- 漏掉从没在 prerequisites 里出现的孤立课程,初始化入队时它们也要一起进队。
DFS · 三色标记判环
沿着依赖链往下走,走回链上的点就是环
另一种 O(V+E) 判环方式,适合学习递归中的访问状态;主解法使用入度与队列。
DFS 的思路是「顺着一条依赖链往下走,如果走回了自己,就说明有环」。
用三种状态标记每个节点:
0:还没访问过;1:正在当前的递归链上(在栈里);2:已经确认它后面没有环。
从每个未访问的点出发 DFS:遇到状态 1 的邻居,说明沿着依赖链绕回了自己,立刻返回 false;遇到 0 就继续往下;一个点的所有邻居都走完且没发现环,就把它标成 2。
状态 2 很关键:它让每个点最多被完整展开一次,复杂度保持 O(n + m)。如果只用一个布尔值记「访问过」,就分不清遇到的是祖先(有环)还是别的分支(无环),会误报。
- 1从每个未访问的课程出发 DFS。
- 2进入 u 时标记 state[u] = 1。
- 3遍历 u 的后继:遇到 state = 1 说明成环,返回 false。
- 4遇到 state = 0 的继续递归。
- 5u 的全部后继都安全,标记 state[u] = 2 并返回 false(无环)。
易错点
- 用 bool visited 代替三色标记,会把「指向其他分支」误判成环。
- 忘记在 DFS 结束后把 state 改成 2,同一个点会被反复展开,复杂度退化。
- 递归深度最多 n = 2000,这个规模安全;再大要考虑改成显式栈。
代码与演示
主解法配有图解;对照解法用于比较思路、复杂度或实现方式。
numCourses = 6, prerequisites = [[1,0],[2,0],[3,1],[4,3],[3,4]]建图并统计入度;课程 5 独立,3 与 4 相互依赖。
查看 C++ 代码
延伸阅读
- Kahn 算法的出队顺序本身就是一个合法拓扑序 —— 课程表 II(210)只要把出队的课程记下来即可。
- 有向图判环有两条等价路线:Kahn 计数看「有没有剩余点」,DFS 三色看「有没有回边」。
- 无向图判环用并查集或 DFS 带父亲,不能套用入度:无向边的两端都会互相加度数。