SDE / New Grad · Coding

Amazon 课程表:可完成、修课顺序与具体环路径

Kahn给出顺序,失败后用显式DFS栈提取闭环,避免把所有残留节点误当环。

题目重点:Kahn给出顺序,失败后用显式DFS栈提取闭环,避免把所有残留节点误当环。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 训练边采用(before,after),课程编号0..n-1;LeetCode常见(course,prerequisite)须先反向。
  • 返回一个闭环即可,不枚举所有环或保证最短;重边按重复依赖一致处理。

解题路线

  1. 建立before到after邻接和入度,0入度课程入队,处理课程后减少后继入度。全部n个出队则顺序满足每条依赖。
  2. 若处理不足n,残留包含有向环;残留还可能包含环下游的无环节点,例如0↔1→2,不能把所有残留节点都称为环。
  3. 用白/灰/黑标记DFS。灰节点位于当前path,边指向灰节点时截取path从该节点开始的后缀并再追加起点,给出真实闭环;指向黑节点不是回边证据。
  4. 代码用显式迭代器栈,避免长课程链超过递归深度。灰节点的位置表在退出时删除,与path保持一致;多连通分量逐一启动。
  5. Kahn正确性来自每次仅输出所有前驱已处理的节点;DAG必有0入度点。反之剩余有限图每点有残留前驱,沿前驱追踪必重复,证明存在环。

手动走一遍

训练依赖0→1、1→0、1→2,返回闭环[0,1,0],2虽处理不了但不在环上;空图返回空顺序且无环。

Python 参考实现

下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。

代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。

from collections import deque


def course_order_or_cycle(n, prerequisites):
    """Each edge is (before, after); return (order, None) or (None, closed cycle)."""
    if n < 0:
        raise ValueError("negative course count")
    graph=[[] for _ in range(n)]
    degree=[0]*n
    for before,after in prerequisites:
        if not (0 <= before < n and 0 <= after < n):
            raise ValueError("invalid course")
        graph[before].append(after)
        degree[after]+=1
    ready=deque(i for i in range(n) if degree[i]==0)
    order=[]
    while ready:
        at=ready.popleft()
        order.append(at)
        for nxt in graph[at]:
            degree[nxt]-=1
            if degree[nxt]==0: ready.append(nxt)
    if len(order)==n:
        return order,None
    color=[0]*n
    for root in range(n):
        if color[root]: continue
        path=[root]
        position={root:0}
        color[root]=1
        stack=[(root,iter(graph[root]))]
        while stack:
            at,neighbors=stack[-1]
            nxt=next(neighbors,None)
            if nxt is None:
                stack.pop()
                path.pop()
                position.pop(at)
                color[at]=2
            elif color[nxt]==0:
                color[nxt]=1
                position[nxt]=len(path)
                path.append(nxt)
                stack.append((nxt,iter(graph[nxt])))
            elif color[nxt]==1:
                return None,path[position[nxt]:]+[nxt]
    raise AssertionError("cycle must exist")

复杂度

O(n+e)时间与空间;返回所有环可能指数级,不属于本合同。

易错点与追问

容易失分的边界

  • 边方向反了。
  • 把Kahn残留全体当一个环。
  • 灰黑标记混用。

继续深挖

  • 需要字典序最小修课顺序?
  • 打印所有属于任意环的课程怎样用SCC?
  • 在线新增依赖如何检测?
✓已复制微信号!