当前位置: 首页 > news >正文

【Leetcode hot 100】207.课程表

问题链接

207.课程表

问题描述

你这个学期必须选修 numCourses 门课程,记为 0numCourses - 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 。这是不可能的。

提示:

  • 1 <= numCourses <= 2000
  • 0 <= prerequisites.length <= 5000
  • prerequisites[i].length == 2
  • 0 <= ai, bi < numCourses
  • prerequisites[i] 中的所有课程对 互不相同

问题解答

问题核心是判断有向图是否存在环(课程依赖关系构成有向图,环代表循环依赖,无法完成所有课程)。

问题分析

  • 模型抽象:将课程视为「节点」,先修关系 [ai, bi] 视为「从 bi 指向 ai 的有向边」(学 ai 必须先学 bi)。
  • 核心目标:判断该有向图是否为「有向无环图(DAG)」,若是则返回 true,否则返回 false

解法 1:Kahn 算法(BFS 拓扑排序)

思路
Kahn 算法是基于「入度」的拓扑排序方法,步骤如下:

  1. 构建图结构:用邻接表 adj 存储节点间的依赖关系,用 inDegree 数组存储每个节点的入度(即前置依赖课程数)。
  2. 初始化队列:将所有入度为 0 的节点(无前置依赖的课程)加入队列。
  3. 拓扑遍历:依次取出队列中的节点,减少其所有邻接节点的入度;若邻接节点入度变为 0,加入队列。
  4. 判断结果:统计遍历的节点总数,若等于课程数 numCourses,说明无环(可完成所有课程),否则有环。

Java 代码

import java.util.LinkedList;
import java.util.Queue;
import java.util.ArrayList;
import java.util.List;class Solution {// Kahn 算法(BFS 拓扑排序)public boolean canFinish(int numCourses, int[][] prerequisites) {// 1. 初始化邻接表(存储图)和入度数组List<List<Integer>> adj = new ArrayList<>(); // adj[u] 表示 u 的所有后继节点(学完 u 可学的课程)int[] inDegree = new int[numCourses];       // inDegree[v] 表示 v 的入度(学 v 需先学的课程数)// 给每个课程初始化邻接表项for (int i = 0; i < numCourses; i++) {adj.add(new ArrayList<>());}// 2. 填充邻接表和入度数组for (int[] pre : prerequisites) {int ai = pre[0]; // 目标课程(需先学 bi)int bi = pre[1]; // 先修课程adj.get(bi).add(ai); // 构建 bi -> ai 的边inDegree[ai]++;      // ai 的入度 +1(多了一个前置依赖)}// 3. 初始化队列:入度为 0 的节点(无前置依赖的课程)Queue<Integer> queue = new LinkedList<>();for (int i = 0; i < numCourses; i++) {if (inDegree[i] == 0) {queue.offer(i);}}// 4. 拓扑遍历,统计可完成的课程数int count = 0;while (!queue.isEmpty()) {int curr = queue.poll(); // 取出当前可学的课程count++;                 // 完成课程数 +1// 遍历当前课程的所有后继课程,减少其入度for (int next : adj.get(curr)) {inDegree[next]--;// 若后继课程入度变为 0,加入队列(可学了)if (inDegree[next] == 0) {queue.offer(next);}}}// 5. 若完成的课程数等于总课程数,说明无环;否则有环return count == numCourses;}
}

解法 2:DFS 环检测

思路
DFS 基于「递归栈」检测环:通过状态数组标记节点的访问状态,判断递归过程中是否回到「正在访问的节点」(即环)。

  1. 状态定义
    • 0:未访问过;
    • 1:正在访问(处于当前递归栈中,未回溯完成);
    • 2:已访问(递归完成,无环)。
  2. 递归逻辑
    • 遍历每个未访问节点,启动 DFS;
    • 若当前节点状态为 1,说明遇到环,返回 false
    • 标记当前节点为 1(进入递归栈),递归访问所有邻接节点;
    • 递归完成后,标记当前节点为 2(退出递归栈);
  3. 结果判断:若所有节点遍历完成未检测到环,返回 true

Java 代码

import java.util.ArrayList;
import java.util.List;class Solution {// DFS 环检测public boolean canFinish(int numCourses, int[][] prerequisites) {// 1. 构建邻接表(存储图)List<List<Integer>> adj = new ArrayList<>();for (int i = 0; i < numCourses; i++) {adj.add(new ArrayList<>());}for (int[] pre : prerequisites) {int ai = pre[0];int bi = pre[1];adj.get(bi).add(ai); // bi -> ai 的边(学 ai 需先学 bi)}// 2. 状态数组:0=未访问,1=正在访问(递归栈中),2=已访问int[] status = new int[numCourses];// 3. 遍历所有节点,检测环for (int i = 0; i < numCourses; i++) {if (status[i] == 0) { // 未访问的节点才需要 DFSif (hasCycle(i, adj, status)) {return false; // 检测到环,直接返回 false}}}// 4. 无环,返回 truereturn true;}// 递归函数:检测从 curr 节点出发是否有环private boolean hasCycle(int curr, List<List<Integer>> adj, int[] status) {// 若当前节点正在访问(递归栈中),说明有环if (status[curr] == 1) {return true;}// 若当前节点已访问,无环if (status[curr] == 2) {return false;}// 标记当前节点为「正在访问」status[curr] = 1;// 递归访问所有邻接节点(后继课程)for (int next : adj.get(curr)) {if (hasCycle(next, adj, status)) {return true; // 子节点有环,当前也有环}}// 递归完成,标记当前节点为「已访问」status[curr] = 2;// 无环return false;}
}

复杂度分析

两种解法的时间/空间复杂度一致:

  • 时间复杂度O(V + E),其中 V = numCourses(节点数),E = prerequisites.length(边数)。需遍历所有节点和边。
  • 空间复杂度O(V + E),邻接表存储边需 O(E),队列/递归栈/状态数组存储节点需 O(V)

示例验证

  • 示例 1numCourses=2, prerequisites=[[1,0]]
    邻接表:adj[0] = [1], adj[1] = [],入度 [0,1]。Kahn 算法队列先入 0,取出后 1 入度变为 0,最终 count=2,返回 true
  • 示例 2numCourses=2, prerequisites=[[1,0],[0,1]]
    邻接表:adj[0] = [1], adj[1] = [0],入度 [1,1]。Kahn 队列无初始节点,count=0≠2,返回 false;DFS 会检测到状态 1 的节点,返回 false
http://www.dtcms.com/a/414522.html

相关文章:

  • 搜索引擎高级搜索技巧
  • 2.3 物理层设备 (答案见原书 P48)
  • 华为OBS obsutil使用
  • 租购同权七年之痒:政策善意如何变现?
  • 【Linux操作系统】基础开发工具
  • 老年ai模拟恋爱抖音快手微信小程序看广告流量主开源
  • 知名的网站制作公司需要多少钱企业宣传网站模板下载
  • 深圳横岗做网站的网站品牌形象设计怎么做
  • 社区网站推广方案百度百家号注册
  • 编程竞赛高频考点
  • Linux 程序使用 STDOUT 打印日志导致程序“假死”?一次线上 Bug 的深度排查与解决
  • (一)routeros命令笔记:开局篇
  • 网站推广模式一份完整的项目计划书
  • 基于STM32设计的智能安全头盔_299
  • ​VR应急安全学习机,提升应对自然灾害时自救互救的应急技能
  • app网站建设公司竞彩网站建设
  • pytorch基本运算-torch.normal()函数输出多维数据时,如何绘制正态分布函数图
  • OpenCV2-图像基本操作-阈值与平滑处理-形态学-梯度运算
  • 【开题答辩全过程】以 springboot+美食电子商城的设计与实现为例,包含答辩的问题和答案
  • MySQL所有关键字详细含义说明
  • MySQL表压缩:用CPU换I/O的秘密武器
  • 做外贸网站需要缴什么税重庆高端网站建设价格
  • java面试day5 | 消息中间件、RabbitMQ、kafka、高可用机制、死信队列、消息不丢失、重复消费
  • 时序数据库选型指南:如何为企业选择合适的时序数据库解决方案
  • 【iOS】alloc、init、new
  • 做网站的开发心得wordpress是不是一定要买服务器
  • AI觉醒:小白的大模型冒险记 第10章:故事续写竞技场 - 实战演练
  • 网站的形成贵州省住房和城乡建设官方网站
  • python知识点
  • LeetCode 5.最长回文字符串