算法题 课程表

LeetCode 207. 课程表

问题描述

给定课程总数 numCourses 和课程依赖关系 prerequisites,判断是否能完成所有课程。依赖关系 prerequisites[i] = [a_i, b_i] 表示学习课程 a_i 前必须先完成课程 b_i

示例

输入:numCourses = 2, prerequisites = [[1,0]]
输出:true
解释:需要先完成课程 0 才能学习课程 1,可以完成。

输入:numCourses = 2, prerequisites = [[1,0],[0,1]]
输出:false
解释:课程 0 和课程 1 互相依赖,形成环,无法完成。

算法思路:拓扑排序(Kahn算法)

  1. 构建有向图:使用邻接表表示课程依赖关系。
  2. 统计入度:记录每门课程的前置课程数量。
  3. 初始化队列:将所有入度为 0 的课程加入队列。
  4. BFS 处理
    • 从队列中取出课程,计数加 1。
    • 将其后继课程的入度减 1。
    • 若后继课程入度为 0,加入队列。
  5. 检测环:若完成课程数 ≠ 总课程数,说明存在环。

代码实现(拓扑排序)

import java.util.*;

class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        // 1. 构建邻接表表示图
        List<List<Integer>> graph = new ArrayList<>();
        for (int i = 0; i < numCourses; i++) {
            graph.add(new ArrayList<>());
        }
        
        // 2. 入度数组初始化
        int[] inDegree = new int[numCourses];
        
        // 3. 遍历依赖关系,填充邻接表和入度数组
        for (int[] edge : prerequisites) {
            int from = edge[1];  // 起点课程(前置课程)
            int to = edge[0];    // 终点课程(当前课程)
            graph.get(from).add(to); // 添加边:from -> to
            inDegree[to]++;         // 终点课程入度增加
        }
        
        // 4. 初始化队列,将所有入度为0的课程加入队列
        Queue<Integer> queue = new LinkedList<>();
        for (int i = 0; i < numCourses; i++) {
            if (inDegree[i] == 0) {
                queue.offer(i);
            }
        }
        
        // 5. BFS遍历:记录已完成的课程数
        int completedCourses = 0;
        while (!queue.isEmpty()) {
            int current = queue.poll(); // 取出当前课程
            completedCourses++;         // 完成课程计数
            
            // 遍历当前课程的所有后继课程
            for (int next : graph.get(current)) {
                inDegree[next]--; // 后继课程入度减1
                // 若后继课程入度为0,加入队列
                if (inDegree[next] == 0) {
                    queue.offer(next);
                }
            }
        }
        
        // 6. 判断是否所有课程都完成
        return completedCourses == numCourses;
    }
}

代码注释

代码说明
List<List<Integer>> graph邻接表存储图结构:索引表示课程,列表存储该课程的后继课程
int[] inDegree入度数组:inDegree[i] 表示课程 i 的前置课程数量
graph.get(from).add(to)添加依赖关系边:fromto(需先完成 from 才能学 to
inDegree[to]++课程 to 的入度增加(因为新增一个前置依赖)
queue.offer(i)入度为 0 的课程加入队列(无前置依赖,可直接学习)
inDegree[next]--学完当前课程后,后继课程 next 的依赖减少
completedCourses == numCourses判断是否所有课程都被处理(相等则无环)

算法分析

  • 时间复杂度:O(n + m)
    • n 为课程数(构建邻接表)
    • m 为依赖关系数(遍历边)
  • 空间复杂度:O(n + m)
    • 邻接表存储所有边(O(m))
    • 入度数组和队列(O(n))

执行过程

输入numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]

  1. 构建图
    • 0 → [1, 2]
    • 1 → [3]
    • 2 → [3]
    • 3 → []
  2. 入度数组[0, 1, 1, 2]
  3. 初始化队列:课程 0 入队(入度=0)
  4. BFS
    • 处理课程 0:计数=1,后继课程 1 和 2 入度减为 0 后入队
    • 处理课程 1:计数=2,后继课程 3 入度减为 1
    • 处理课程 2:计数=3,后继课程 3 入度减为 0 后入队
    • 处理课程 3:计数=4
  5. 结果:完成课程数=4,返回 true

测试用例

public static void main(String[] args) {
    Solution solution = new Solution();
    
    // 无依赖关系
    System.out.println(solution.canFinish(3, new int[][]{})); // true
    
    // 有环情况
    System.out.println(solution.canFinish(2, new int[][]{{1,0},{0,1}})); // false
    
    // 复杂依赖无环
    int[][] deps = {{1,0},{2,1},{3,2}};
    System.out.println(solution.canFinish(4, deps)); // true
    
    // 复杂依赖有环
    int[][] depsCycle = {{1,0},{2,1},{0,2}};
    System.out.println(solution.canFinish(3, depsCycle)); // false
    
    // 多分支有环
    int[][] multiBranch = {{1,0},{2,0},{3,1},{1,3}};
    System.out.println(solution.canFinish(4, multiBranch)); // false
}

关键点

  1. 拓扑排序核心:通过入度管理课程依赖,逐步解锁可学习课程。
  2. 环检测机制:若存在环,则环内课程的入度永不为 0,无法加入队列。
  3. 边界处理
    • 无依赖关系:所有课程入度=0,直接完成。
    • 单课程:无需处理,直接返回 true
  4. 实际应用:课程安排、任务调度、编译顺序等依赖关系分析场景。

提示:若问题扩展为「输出学习顺序」,只需在 BFS 过程中记录出队顺序即可得到拓扑序列。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值