LeetCode 207. 课程表(Kahn 拓扑排序 BFS)

0 阅读3分钟

image.png

一、核心思路

本题等价于判断有向图是否存在环

  • 课程对应图的节点;prerequisites[i] = [a,b]:学习 a 必须先学 b → 构建有向边 b → a
  • Kahn(BFS 拓扑排序)思想:
  1. 构建邻接表存储图,用数组记录每个节点的入度(该课程还有几门前置课未完成)
  2. 将入度为 0 的节点(无前置课程)加入队列
  3. 不断从队列取出节点,计数 + 1;它的后继节点入度 - 1,后继入度变为 0 则入队
  4. 最后比较:成功遍历的节点数 count 是否等于总课程数。相等说明无环,可以修完所有课;count 更小说明存在环,无法完成。

二、完整 Java 代码

import java.util.*;
class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        //邻接表,存每个节点的后继节点
        List<List<Integer>> adj = new ArrayList<>();
        //inDegree:记录每门课的入度(前置课程数量)
        int[] inDegree = new int[numCourses];
        //初始化邻接表,给每一个课程分配一个空列表
        for(int i = 0; i < numCourses; i++){
            adj.add(new ArrayList<>());
        }
        //建图 + 更新入度
        for(int[] p : prerequisites){
            int a = p[0];
            int b = p[1];
            adj.get(b).add(a); //边 b -> a,b学完才可以学a
            inDegree[a]++;
        }
        Queue<Integer> q = new LinkedList<>();
        //入度为0的课程入队(可以直接学习)
        for(int i = 0; i < numCourses; i++){
            if(inDegree[i] == 0){
                q.offer(i);
            }
        }
        int count = 0; //统计已经学完的课程
        while(!q.isEmpty()){
            int cur = q.poll();
            count++;
            //遍历cur所有后继课程
            for(int next : adj.get(cur)){
                inDegree[next]--;
                if(inDegree[next] == 0){
                    q.offer(next);
                }
            }
        }
        //所有课程都遍历到→无环;否则有环
        return count == numCourses;
    }
}

三、实例表格演示

示例 1:numCourses = 2,prerequisites = [[1,0]]

边:0 → 1

节点初始入度
00
11
步骤队列count操作
1[0]0初始化,0 入度为 0 入队
2[ ]1弹出 0,count=1;后继 1 入度减为 0,1 入队
3[1]1队列现在是 [1]
4[ ]2弹出 1,count=2;无后继

判断:2 == 2 → 返回 true ✅

示例 2:numCourses = 2,prerequisites = [[1,0],[0,1]]

边:0 → 1,1 → 0

节点初始入度
01
11
步骤队列count操作
1[ ]0没有入度 = 0 的节点,队列为空,循环不执行

判断:0 != 2 → 返回 false ❌

四、复杂度分析

  • 时间复杂度:(O(n+m)),n 是课程总数,m 是前置条件数量,每个节点、每条边只会访问一次
  • 空间复杂度:(O(n+m)),邻接表、入度数组、队列占用空间

五、易错点总结

  1. 边方向极易写反:[a,b]代表学 a 要先学 b,是b→a,写反整个拓扑排序失效
  2. 必须先初始化邻接表,给每个课程分配空列表,否则adj.get()下标越界
  3. 环内节点入度永远无法变成 0,不会被 count 统计,count 一定小于总课程数

六、个人感悟

拓扑排序专门用来解决有向图判环、依赖顺序安排类题目,Kahn BFS 模板固定。核心记住:入度代表还剩多少依赖,依赖全部消除(入度 = 0)才可以开始学习该节点。