leetcode hot100 207. 课程表 检测有向图中是否存在环 medium Kahn 算法 入度表 BFS 图论

这个图是不是一个有向无环图(DAG)?

如果有环(比如 A 依赖 B,B 依赖 C,C 又依赖 A),无法毕业

核心思路

如果在递归过程中,发现下一个节点在递归栈中(正在访问中),则找到了环。


开始节点为:没有前置课程的课程,把题目全部放入队列

  1. 然后弹出队头,count += 1,表示修完了这门课
  2. 队头所有的后续课程 -1,同时检查后续课程还有没有前置课程了?没有就加入队列
  3. 循环弹出,直到没有可以修的课程
  4. 已经修完的课=总课程数?

需要两个表:

  • indegrees(入度表) :"这门课还有多少个前提条件没完成"。
    • indegrees3 = 2,说明课程 3 有 2 门先修课。你现在不能修它
  • adjacency(邻接表) :字典,键(Key)是课程 A,值(Value)是一个列表,里面装着所有依赖 A 的课程。
    • adjacency1 = 0。这意味着:"修完 1 之后,记得去通知 0

例如: 修完B可以修A,C邻接表为:

key value
B A,C
python 复制代码
from collections import deque, defaultdict

class Solution:
    def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:

        # 1. 统计入度和邻接表
        indegrees = [0] * numCourses   # "这门课还有多少个前提条件没完成" [0, 0, ...]
        adjacency = defaultdict(list)   # 依赖课程字典  {0: [], 1: [], ...}

        # prerequisites = [[1,0]]   修完0,可以修1
        # 想学cur(课程1),必先学pre(课程0)。 边:pre -> cur
        for cur, pre in prerequisites:
            adjacency[pre].append(cur)   # 修完pre,可以修cur
            indegrees[cur] += 1    # cur 需要多少先修课

        # 2. 将所有不需要先修课的节点入队
        q = deque()
        for i in range(numCourses):   # 遍历每一门课(从 0 到 numCourses - 1)
            if indegrees[i] == 0:
                q.append(i)  #  课程标号入队

        # 3. BFS
        count = 0  # 统计修完的课程数
        while q:
            pre = q.popleft()
            count += 1    #修完了这门课

            # 队头所有的后续课程 -1,同时检查后续课程还有没有前置课程了?没有就加入队列
            for cur in adjacency[pre]:    # 队头pre的后续课程列表:adjacency[pre]
                indegrees[cur] -= 1       # 前置课程-1
                if indegrees[cur] == 0:   # 没有前置课程了,加入队列
                    q.append(cur)

        # 4. 如果修完的课程数等于总课数,说明无环
        return count == numCourses

时间复杂度:$O(V + E)

VVV 是课程总数(numCourses),EEE 是先修关系的数量(prerequisites 的长度)。

  1. 建图阶段:遍历 prerequisites, 填充邻接表和欠债数。这个过程耗时 O(E)O(E)O(E)
  2. 初始化队列:遍历 indegrees,将所有不需要先修课的节点入队。这个过程耗时 O(V)O(V)O(V)
  3. BFS 遍历:每个课程(节点)最多进出队列一次,总共 O(V)O(V)O(V)。每当修完一门课,我们会遍历它的"解锁名单"。由于所有名单加起来的总长度就是边的总数,所以这部分总共耗时 O(E)O(E)O(E)

O(E)+O(V)+O(V)+O(E)O(E) + O(V) + O(V) + O(E)O(E)+O(V)+O(V)+O(E),简化后就是 O(V+E)O(V + E)O(V+E)

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

  1. adjacency(邻接表):存储了所有的先修关系,空间占用为 O(E)O(E)O(E)。
  2. indegrees (入度表):存储了 VVV 门课的先修数量,空间占用为 O(V)O(V)O(V)。
  3. 队列 (Queue):在最坏情况下(比如所有课都是基础课,互不依赖),队列中可能同时存在所有课程,空间占用为 O(V)O(V)O(V)。总计:把这些加起来,空间复杂度同样是 O(V+E)O(V + E)O(V+E)。
相关推荐
To_OC17 小时前
LC 1 两数之和:面试第一道必考题,暴力解法直接被面试官 pass
javascript·算法·leetcode
想吃火锅10057 天前
【leetcode】121.买卖股票的最佳时机js/c++
算法·leetcode·职场和发展
凌波粒7 天前
LeetCode--491.递增子序列(回溯算法)
数据结构·算法·leetcode
退休倒计时7 天前
【每日一题】LeetCode 146. LRU 缓存 TypeScript
算法·leetcode·缓存·typescript
小欣加油7 天前
leetcode3612 用特殊操作处理字符串I
数据结构·c++·算法·leetcode·职场和发展
凌波粒7 天前
LeetCode--90.子集II(回溯算法)
数据结构·算法·leetcode
凌波粒7 天前
LeetCode--46.全排列(回溯算法)
数据结构·算法·leetcode
吃着火锅x唱着歌7 天前
LeetCode 2530.执行K次操作后的最大分数
数据结构·算法·leetcode
sheeta19987 天前
LeetCode 每日一题笔记 日期:2026.06.16 题目:3612. 字符串特殊符号处理
笔记·算法·leetcode
CoderYanger7 天前
A.每日一题:2095. 删除链表的中间节点
java·数据结构·程序人生·leetcode·链表·面试·职场和发展