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)。
相关推荐
longlongzihan3 小时前
LeetCode 5. 最长回文子串:从暴力匹配到中心扩展的思维演进
c++·算法·leetcode
苦瓜打怪兽3 小时前
leetcode 热题100(11. 盛最多水的容器)
python·算法·leetcode
All for pursuit.8 小时前
【回溯-6】79.单词搜索
数据结构·c++·算法·leetcode
Navigator_Z8 小时前
LeetCode //C - 1281. Subtract the Product and Sum of Digits of an Integer
c语言·算法·leetcode
旖旎夜光10 小时前
LeetCode 1576: 替换所有的问号(模拟) —— 题解
c++·学习·算法·leetcode·力控
longlongzihan10 小时前
回溯法详解:LeetCode 46. 全排列
算法·leetcode·回溯
圣保罗的大教堂12 小时前
leetcode 3532. 针对图的路径存在性查询 I 中等
leetcode
longlongzihan1 天前
从LeetCode 70. 爬楼梯看动态规划:如何精准推导状态转移方程?
c++·算法·leetcode·动态规划
言乐61 天前
Python区分广度优先深度优先宽度优先的区别
python·算法·深度优先·广度优先·宽度优先
Rnan-prince1 天前
LeetCode 股票买卖系列:一个动态规划状态机模板通吃四题
leetcode·动态规划