【中等】力扣算法题解析LeetCode210:课程表 II

关注底部名片达文汐,即可获得本题完整源码

题目来源:LeetCode210:课程表 II

问题抽象: 设计算法求解课程安排问题的可行学习序列(拓扑排序),需满足以下核心需求:

  1. 依赖关系建模

    • 给定课程总数 numCourses(节点数 0numCourses-1);
    • 先修条件 prerequisites:边列表 [a_i, b_i] 表示 b_i → a_ib_ia_i 的前置课程);
    • 目标:生成满足所有依赖关系的课程学习顺序(拓扑序)。
  2. 可行性判定

    • 若依赖图无环(存在拓扑序),输出任意有效序列;
    • 若依赖图有环(无法完成所有课程),输出空数组。
  3. 核心算法策略

    • Kahn 算法(入度表)
      • 统计每个节点入度(前置课程数量);
      • 队列维护当前可学课程(入度为 0);
      • 每学完一门课,更新其后续课程的入度(入度归零则入队)。
    • DFS 逆序
      • 后序遍历节点,反转序列得拓扑序(需检测环)。
  4. 复杂度约束

    • 时间复杂度 O(V+E)(节点数 V + 边数 E);
    • 空间复杂度 O(V+E)(邻接表 + 入度表 + 队列)。
  5. 边界处理

    • 课程数 0 时返回空数组 []
    • 无先修条件时:返回任意顺序(如 [0,1,...,numCourses-1]);
    • 环检测:如 [[1,0],[0,1]](两门课互斥),输出 []
    • 多解场景:输出任意有效解(如 [0,2,1,3][0,1,2,3] 均可)。

输入:整数 numCourses1 ≤ numCourses ≤ 2000);先修条件列表 prerequisites0 ≤ prerequisites.length ≤ 5000,每项为长度 2 的列表)
输出:整数数组(拓扑序列,长度 =numCourses;若不可行则返回空数组 []


解题思路

本题是一个典型的拓扑排序问题。拓扑排序用于解决有向无环图(DAG)中节点依赖关系的线性排序问题。具体思路如下:

  1. 构建图与入度统计

    • 使用邻接表表示课程依赖关系:graph[i] 存储课程 i 的所有后续课程。
    • 统计每个课程的入度(即指向该课程的边数)。
  2. 初始化队列

    • 将所有入度为 0 的课程加入队列(这些课程没有先修课,可立即学习)。
  3. BFS 拓扑排序

    • 从队列中取出课程(入度为 0),加入结果数组。
    • 遍历该课程的所有后续课程,将其入度减 1(相当于删除依赖关系)。
    • 若后续课程的入度减至 0,则加入队列。
  4. 结果验证

    • 若结果数组长度等于课程总数,则返回该数组(所有课程可完成)。
    • 否则返回空数组(存在循环依赖)。

亮点

  • 使用数组存储邻接表(避免 ArrayList 扩容开销)。
  • 使用数组存储入度,提高访问效率。
  • 使用队列进行 BFS,确保时间复杂度为 O(N + E)(N 为节点数,E 为边数)。

代码实现(Java版)

class Solution {
    public int[] findOrder(int numCourses, int[][] prerequisites) {
        // 初始化邻接表(使用数组存储,每个元素是一个列表)
        List<Integer>[] graph = new ArrayList[numCourses];
        for (int i = 0; i < numCourses; i++) {
            graph[i] = new ArrayList<>();
        }
        // 初始化入度数组
        int[] inDegree = new int[numCourses];
        
        // 构建图并统计入度
        for (int[] edge : prerequisites) {
            int a = edge[0]; // 目标课程
            int b = edge[1]; // 先修课程
            graph[b].add(a);  // b -> a 的有向边
            inDegree[a]++;    // a 的入度加1
        }
        
        // 初始化队列(存储入度为0的节点)
        Queue<Integer> queue = new LinkedList<>();
        for (int i = 0; i < numCourses; i++) {
            if (inDegree[i] == 0) {
                queue.offer(i);
            }
        }
        
        // 结果数组及索引
        int[] result = new int[numCourses];
        int index = 0;
        
        // BFS 拓扑排序
        while (!queue.isEmpty()) {
            int course = queue.poll();
            result[index++] = course; // 将当前课程加入结果
            // 遍历当前课程的所有后续课程
            for (int next : graph[course]) {
                inDegree[next]--;     // 后续课程入度减1
                if (inDegree[next] == 0) {
                    queue.offer(next); // 入度为0则加入队列
                }
            }
        }
        
        // 检查是否所有课程都被安排
        if (index == numCourses) {
            return result;
        } else {
            return new int[0]; // 存在环,返回空数组
        }
    }
}

代码说明

  1. 数据结构

    • graph:邻接表,存储每个课程指向的后续课程。
    • inDegree:数组,存储每个课程的入度(依赖的课程数量)。
    • queue:队列,用于 BFS 遍历入度为 0 的课程。
    • result:结果数组,存储拓扑排序序列。
  2. 关键步骤

    • 建图:遍历 prerequisites,构建邻接表并更新入度数组。
    • 初始化队列:将所有入度为 0 的课程加入队列。
    • BFS 遍历
      • 取出队首课程加入结果数组。
      • 将其后续课程的入度减 1,若入度变为 0 则入队。
    • 验证结果:若结果数组长度等于课程总数,则返回;否则返回空数组。

提交详情(执行用时、内存消耗)

在这里插入图片描述

我的名片👇👇👇👇👇👇👇👇👇👇👇

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

达文汐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值