Skip to content

207. 课程表 Medium

你这个学期必须选修 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 。这是不可能的。

解题思路

输入: 一个整数表示课程数 numCourses,一个二维数组 prerequisites 表示前置课程要求

输出: 判断是否可以完成所有课程的学习

本题属于DFS 检测环类问题。

  1. 建图:把课程依赖关系转成邻接表,例如 b → a 表示 b 指向 a。
  2. DFS 遍历:
    • 用 visited 标记访问状态:
      • 0 = 未访问
      • 1 = 正在访问(递归栈中)
      • 2 = 已完成
    • 在 DFS 过程中:
      • 如果访问到 1 → 出现环,返回 false。
      • 如果访问到 2 → 已处理过,返回 true。
      • 遍历所有课程,确保每个节点都检查一遍。

代码实现

python
class Solution:
    def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
        # 用字典存储图:key = 先修课程, value = 依赖它的后续课程列表
        graph = {}

        # 构建有向图
        for a, b in prerequisites:
            if b in graph:
                graph[b].append(a)
            else:
                graph[b] = [a]
        
        # 访问状态数组
        # 0 = 未访问, 1 = 正在访问(递归栈中), 2 = 已完成
        visited = [0] * numCourses

        def dfs(course):
            # 如果遇到正在访问的节点,说明有环
            if visited[course] == 1:
                return False
            # 如果已经访问完成,直接返回 True
            if visited[course] == 2:
                return True
            
            # 标记为正在访问
            visited[course] = 1

            # 遍历后继课程,递归检查
            if course in graph:
                for next in graph[course]:
                    if not dfs(next):
                        return False
            
            # 所有后继课程都能完成,标记为已完成
            visited[course] = 2
            return True
        
        # 逐一检查所有课程
        for i in range(numCourses):
            if not dfs(i):
                return False  # 如果发现环,直接返回 False
        
        return True  # 没有环,可以完成所有课程
javascript
var canFinish = function(numCourses, prerequisites) {
    // 用邻接表存储图:key = 先修课程, value = 依赖它的后续课程列表
    const graph = new Map();
    for (let [a, b] of prerequisites) {
        if (graph.get(b)) {
            graph.get(b).push(a);
        } else {
            graph.set(b, [a]);
        }
    }

    // visited 数组表示每个节点的访问状态
    // 0 = 未访问, 1 = 正在访问(递归栈中), 2 = 已完成
    const visited = new Array(numCourses).fill(0);

    function dfs(course) {
        // 如果节点正在访问 → 说明遇到环
        if (visited[course] === 1) return false;
        // 如果节点已经访问完成 → 直接返回 true
        if (visited[course] === 2) return true;

        // 标记为正在访问
        visited[course] = 1;

        // 遍历该课程的所有后续课程
        if (graph.get(course)) {
            for (let next of graph.get(course)) {
                // 如果发现环,直接返回 false
                if (!dfs(next)) return false;
            }
        }

        // 所有后续课程检查完毕,标记为已完成
        visited[course] = 2;
        return true;
    }

    // 对每门课进行 DFS 检查
    for (let i = 0; i < numCourses; i++) {
        if (!dfs(i)) return false; // 如果有环,直接返回 false
    }

    // 没有环 → 可以完成所有课程
    return true;
};

复杂度分析

时间复杂度:O(V + E) (课程数 + 先修关系数)

空间复杂度:O(V + E)

链接

207 国际版

207 中文版