课程表

课程表题的本质不是排课,而是判断一个有向图里有没有环;没环就能学完,有环就一定学不完。

问题描述

LeetCode 207 - 课程表

你需要选n门课程,记为0到n-1。在选某些课程之前需要先选其他课程。例如,想要学习课程0,需要先完成课程1,表示为[0,1]。给定课程总数和先修课程列表,判断是否可能完成所有课程的学习。

输入: numCourses = 2, prerequisites = [[1,0]]
输出: true
说明: 总共2门课,学习课程1之前需要先学习课程0,可以完成

输入: numCourses = 2, prerequisites = [[1,0],[0,1]]
输出: false
说明: 存在循环依赖,无法完成

解题思路

  • 环检测:判断有向图是否存在环
  • 拓扑排序:使用Kahn算法或DFS
  • 入度统计:Kahn算法统计入度
  • 三色标记:DFS使用三色标记检测环

为什么拓扑排序能做

把一条依赖 [a, b] 看成:

b -> a

意思是先学 b,再学 a。
如果这张有向图存在环,就说明有一组课程互相依赖,谁都没法先开始。

Kahn 算法的思路很直接:

  1. 先把所有入度为 0 的课放进队列。
  2. 每学完一门课,就删掉它对后续课程的影响。
  3. 如果最后能删完所有点,说明无环;否则有环。

Go 代码

Go 实现

func canFinish(numCourses int, prerequisites [][]int) bool {
    graph := make([][]int, numCourses)
    inDegree := make([]int, numCourses)
 
    for _, prereq := range prerequisites {
        course, pre := prereq[0], prereq[1]
        graph[pre] = append(graph[pre], course)
        inDegree[course]++
    }
 
    queue := []int{}
    for i := 0; i < numCourses; i++ {
        if inDegree[i] == 0 {
            queue = append(queue, i)
        }
    }
 
    count := 0
    for len(queue) > 0 {
        node := queue[0]
        queue = queue[1:]
        count++
 
        for _, neighbor := range graph[node] {
            inDegree[neighbor]--
            if inDegree[neighbor] == 0 {
                queue = append(queue, neighbor)
            }
        }
    }
 
    return count == numCourses
}

易错点

课程表这题最容易错的是边方向和“入度代表谁依赖谁”。

  • [a, b] 不是 a -> b,而是 b -> a。
  • 入度统计的是“还有多少前置课程没学”。
  • 最后判断条件不是队列空没空,而是弹出的课程数是否等于总课程数。
  • 这题可以用 Kahn,也可以用 DFS 判环,但不要把两套状态混着写。

复杂度分析

指标复杂度说明
时间复杂度O(V+E)V是课程数,E是依赖关系数
空间复杂度O(V+E)图和队列

相关主题


返回:图算法 | 算法学习导航