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)。
相关推荐
土司大王1 小时前
LeetCode hot100——两两交换链表中的节点
算法·leetcode·职场和发展
zander2583 小时前
LeetCode 300. 最长递增子序列
算法·leetcode·深度优先
欧叶冲冲冲3 小时前
Python常见数据结构的CRUD(LeetCode高频版速查)
数据结构·python·leetcode
圣保罗的大教堂4 小时前
leetcode 3622. 判断整除性 简单
leetcode
Lyyaoo.4 小时前
【普通数组】【中等】除了自身以外数组的乘积
数据结构·算法·leetcode
圣保罗的大教堂6 小时前
leetcode 2996. 大于等于顺序前缀和的最小缺失整数 简单
leetcode
圣保罗的大教堂6 小时前
leetcode 3702. 按位异或非零的最长子序列 中等
leetcode
青 春 记 忆8 小时前
LeetCode 206. 反转链表|Python 解法详解
python·leetcode·链表
青 春 记 忆10 小时前
LeetCode 226. 翻转二叉树|Python 解法详解
python·算法·leetcode