leetcode / Graphs / Topological sort core

Topological sort

Some things must happen before others: courses before the courses that need them, build steps before the steps that use their output, letters before the letters that follow them in a dictionary. A topological sort lines everything up so that every "before" is respected. It exists exactly when there is no circular dependency, which makes the same algorithm the standard cycle detector for directed graphs.

On this page
  1. Problem
  2. Intuition
  3. Mechanics
  4. Kahn's algorithm
  5. The depth-first version
  6. Rounds and longest chains
  7. Deriving the edges
  8. Worked example
  9. Implementation
  10. Variations and edge cases
  11. Complexity and tradeoffs
  12. Problems
  13. Medium
  14. Hard
  15. Also uses this pattern
  16. Connections
  17. Read first
  18. Read next
  19. Check yourself
  20. References

Problem

The input is a set of items and a set of rules of the form "a must come before b". You are asked for any valid order, for whether one exists, for the earliest round in which each item can happen, or for something that can be derived from the order, such as a hidden alphabet. Sometimes the rules are not given but must be read from the data first.

SignalPatternWhy
"Prerequisites", "dependencies", "must finish before"Topological sortThe rules are directed edges; an order that respects all of them is a topological order.
"Can all of them be completed"Kahn's algorithm; check that every node was processedLeftover nodes sit on or behind a cycle.
"Order implied by a sorted list"Build edges from adjacent items, then sortOnly neighbors in a sorted list reveal a rule; the first differing position is the evidence.
"Minimum number of rounds / semesters"Kahn's algorithm by layersThe layer a node lands in is the length of the longest dependency chain ending at it.
"Roots giving the shortest tree"Peel leaves layer by layerIt is Kahn's algorithm run on degrees; the last survivors are the centers.
Rules may form a loop and a cycle needs reportingThree-state DFSThe path stack names the cycle; Kahn only says one exists.

Intuition

Getting dressed has an order: socks before shoes, trousers before belt. You do not need to plan it all at once. At any moment, pick something that has no unmet requirement, do it, and cross it off every other item's list of requirements. If you can always find such an item, you finish with a valid order. If at some point everything left still has an unmet requirement, you have found a circle (shoes need socks need shoes) and no order exists.

Kahn's algorithm is exactly that. It counts for each node how many prerequisites are unfinished (its in-degree), starts with the nodes that have none, and each time it finishes a node it reduces the count of every node that depended on it. The depth-first version reaches the same set of answers from the other side: the node that finishes last in a depth-first search has nothing after it that is unfinished, so listing nodes in reverse finishing order puts every node before everything it points to.

A dependency graph arranged by Kahn rounds A directed graph of 6 nodes with edges 5 to 2, 5 to 0, 4 to 0, 4 to 1, 2 to 3, 3 to 1. Nodes are in columns by round. Round 1: 4, 5. Round 2: 0, 2. Round 3: 3. Round 4: 1. Every arrow points from an earlier column to a later one. 4 5 0 2 3 1 round 1 round 2 round 3 round 4

The small blue dot marks the arrow's head. Every dependency goes from a column on the left to a column on the right, which is what a topological order means. Nodes in one column are independent of each other and could happen in parallel. The diagram scrolls sideways on a narrow screen.

Mechanics

Kahn's algorithm

Compute the in-degree of every node. Put the zero in-degree nodes in a queue. Repeatedly remove a node, append it to the answer, and decrement the in-degree of each node it points to; a node whose count reaches zero joins the queue. Finish when the queue is empty. If the answer has fewer nodes than the graph, the rest are blocked by a cycle.

Why it is correct. A node is queued only when every prerequisite has already been appended, so each edge goes forward in the answer. If the answer is short, every node left over still has an unfinished prerequisite that is itself left over; following those prerequisites backwards can never end, so it must revisit a node, which is a cycle. Conversely a cycle's nodes never reach in-degree zero, so they are always left over.

The depth-first version

Run a DFS with three states (unseen, on the path, finished) and append a node to a list the moment it finishes. An edge into an on-path node is a cycle. Otherwise the reversed list is a topological order, because when a node finishes, everything it points to has already finished and sits earlier in the list.

Rounds and longest chains

Process the queue one layer at a time and count layers. A node lands in round k + 1 where k is the longest chain of prerequisites below it, so the number of rounds is the "minimum semesters" answer when any number of items may proceed together. The same counting yields the longest path in a DAG, a task no ordinary graph can do in linear time.

Deriving the edges

Sometimes the graph is hidden. In a list of words sorted by an unknown alphabet, compare each word with the next: the first position where they differ gives one rule, "this letter comes before that one". Later positions say nothing. If the longer word comes first while the shorter is its prefix, the list cannot be sorted under any alphabet.

A cycle stops Kahn's algorithm The same graph with one extra edge from 1 back to 5. Node 4 has no prerequisites and is processed. Nodes 0, 1, 2, 3, 5 are never processed: 5, 2, 3 and 1 form a cycle, and 0 waits on it. 4 5 0 2 3 1 green = processed, amber = stuck, red = the added edge 1 to 5

With the extra edge, only node 4 has in-degree zero. After it is removed, the others are all still waiting on someone who is waiting on them. Node 0 is not on the cycle but is stuck behind it.

Worked example

Six items with rules 5→2, 5→0, 4→0, 4→1, 2→3, 3→1. The in-degrees are 0: 2, 1: 2, 2: 1, 3: 1, 4: 0, 5: 0.

MethodResultNotes
Kahn (one node at a time)4, 5, 0, 2, 3, 14 rounds: {4, 5} {0, 2} {3} {1}
Reverse DFS finishing order5, 4, 2, 3, 1, 0A different valid order
Kahn after adding edge 1→545 nodes stuck: 0, 1, 2, 3, 5
DFS after adding edge 1→5noneCycle detected

The two orders differ and both are correct: a DAG usually has many topological orders, and a problem that wants one specific order must say how to break ties. The number of rounds, 4, is the longest chain 5→2→3→1 counted in nodes.

A hidden alphabet. From the sorted words wrt, wrf, er, ett, rftt, comparing neighbors yields the rules t<f, w<e, r<t, e<r, and sorting them gives the alphabet wertf. The list ["abc", "ab"] is impossible (the helper reports a prefix problem: a word is sorted before its own prefix), and ["z", "x", "z"] demands both z<x and x<z, so the helper reports a cycle and no order.

Peeling leaves. For the tree with edges 3-0, 3-1, 3-2, 3-4, 5-4, removing all leaves takes 1 round and leaves nodes 3 and 4, the roots that give the shortest rooted tree.

Implementation

from collections import defaultdict, deque


def topological_order(n, prerequisites):
    adj = defaultdict(list)
    indegree = [0] * n
    for course, before in prerequisites:
        adj[before].append(course)
        indegree[course] += 1
    queue = deque(i for i in range(n) if indegree[i] == 0)
    order = []
    while queue:
        node = queue.popleft()
        order.append(node)
        for nxt in adj[node]:
            indegree[nxt] -= 1
            if indegree[nxt] == 0:
                queue.append(nxt)
    return order if len(order) == n else []


def can_finish(n, prerequisites):
    return len(topological_order(n, prerequisites)) == n

Variations and edge cases

def alien_order(words):
    letters = {ch for word in words for ch in word}
    adj = {ch: set() for ch in letters}
    indegree = {ch: 0 for ch in letters}
    for first, second in zip(words, words[1:]):
        for a, b in zip(first, second):
            if a != b:
                if b not in adj[a]:
                    adj[a].add(b)
                    indegree[b] += 1
                break
        else:
            if len(first) > len(second):
                return ""
    queue = deque(ch for ch in letters if indegree[ch] == 0)
    out = []
    while queue:
        ch = queue.popleft()
        out.append(ch)
        for nxt in adj[ch]:
            indegree[nxt] -= 1
            if indegree[nxt] == 0:
                queue.append(nxt)
    return "".join(out) if len(out) == len(letters) else ""


def minimum_height_trees(n, edges):
    if n == 1:
        return [0]
    adj = [set() for _ in range(n)]
    for a, b in edges:
        adj[a].add(b)
        adj[b].add(a)
    leaves = [i for i in range(n) if len(adj[i]) == 1]
    while n > 2:
        n -= len(leaves)
        following = []
        for leaf in leaves:
            neighbor = adj[leaf].pop()
            adj[neighbor].discard(leaf)
            if len(adj[neighbor]) == 1:
                following.append(neighbor)
        leaves = following
    return leaves


def longest_chain(n, edges):
    adj = [[] for _ in range(n)]
    indegree = [0] * n
    for a, b in edges:
        adj[a].append(b)
        indegree[b] += 1
    layer = [i for i in range(n) if indegree[i] == 0]
    depth = 0
    while layer:
        depth += 1
        following = []
        for node in layer:
            for nxt in adj[node]:
                indegree[nxt] -= 1
                if indegree[nxt] == 0:
                    following.append(nxt)
        layer = following
    return depth
  • Edge direction. "Course a requires b" is the edge b→a. Reversing it silently produces a valid but reversed order. Say the direction aloud before coding.
  • Duplicate edges. Counting a repeated edge twice in the in-degree but visiting it once (or the reverse) leaves a node stuck. Deduplicate with a set, or be consistent on both sides.
  • Letters that appear in no rule. In the alien alphabet they must still be in the answer; seed the node set from all letters, not only from edges.
  • Prefix trap. ["abc", "ab"] is invalid even though no letters differ. Check lengths when the comparison loop finds no difference.
  • Several valid orders. If the judge needs a specific one (lexicographically smallest), use a heap in place of the queue.
  • Edge cases: no edges (any order, all nodes), a self-loop (always a cycle), a disconnected graph (Kahn handles it, since all zero in-degree nodes start), and a single node.

Complexity and tradeoffs

  • Time: O(V + E). Each node is queued once and each edge decrements one counter once.
  • Space: O(V + E) for the adjacency list, plus O(V) for in-degrees and the queue.
  • Baseline. Repeatedly scanning for a node with no unfinished prerequisite costs O(V · (V + E)). Keeping a count and a queue is what makes it linear.
  • Kahn or DFS. Kahn is iterative, naturally layered, and detects a cycle by counting. DFS needs recursion (or an explicit stack) but reports the cycle itself and fits problems that already traverse recursively.
  • When it fails. It needs directed edges and no cycle; for "are these connected" with undirected links use union-find. Rules with costs or deadlines on top of the order (critical path with durations) extend the layering into a longest-path dynamic program over the order.

Problems

Course Schedule asks only whether an order exists; Course Schedule II asks for one. Alien Dictionary hides the graph inside adjacent words. Minimum Height Trees uses the same peel-the-boundary idea on an undirected tree. Course Schedule IV and Build a Matrix With Conditions use the order as a stepping stone: reachability between courses, and positions in two independent orders for rows and columns.

Medium

  • 207. Course Schedule Blind 75 Grind 169 NeetCode 150 NeetCode 250
    All courses can be taken exactly when the prerequisite graph has no cycle, which Kahn's algorithm checks by trying to remove every node.
  • 210. Course Schedule II Grind 169 NeetCode 150 NeetCode 250
    Kahn's algorithm's removal order is a valid course order, and returning fewer than n courses means there is a cycle.
  • 310. Minimum Height Trees Grind 169 NeetCode 250
    Repeatedly trim the current leaves; the one or two nodes left at the end are the centres of the tree.
  • 1462. Course Schedule IV NeetCode 250
    Propagate each course's set of prerequisites along a topological order, so every query becomes a set lookup.

Hard

  • 269. Alien Dictionary Blind 75 Grind 169 NeetCode 150 NeetCode 250
    The first differing letter of each adjacent word pair gives an ordering edge, then a topological sort finds the alphabet or a cycle.
  • 2392. Build a Matrix With Conditions NeetCode 250
    The row and column conditions are independent, so two topological sorts give each number's row and column.

Also uses this pattern

These problems have a different home page, but this idea is part of their solution.

Connections

Read first

Read next

No page builds on this one yet.

Topological sort is graph traversal with a count of unmet dependencies. The order it produces is what makes dynamic programming on a DAG possible (solve each node after everything it depends on), and the same "remove the nodes with nothing blocking them" loop returns in shortest-path algorithms.

Check yourself

How does Kahn's algorithm tell you there is a cycle?

It finishes with fewer nodes in the answer than in the graph. The nodes on a cycle never reach in-degree zero, and neither do the nodes that depend on them. In the example with the extra edge, 5 of 6 nodes were left over.

Why do Kahn and the DFS method give different orders for the same graph, and are both right?

A DAG with independent nodes has many valid orders; the algorithms break ties differently. Here they gave 4, 5, 0, 2, 3, 1 and 5, 4, 2, 3, 1, 0. Both put every prerequisite first.

In Alien Dictionary, why can a pair of words contribute at most one rule?

Only the first position where two adjacent words differ is decided by the alphabet; everything after it was decided by that earlier difference. Positions before it are equal and say nothing.

Why must Alien Dictionary check for a longer word before its own prefix?

A prefix is always sorted before the longer word, in every alphabet, so "abc" before "ab" cannot happen. The loop that looks for a differing letter finds none, so the length check is the only place this contradiction can be caught.

References

  • Cormen, Leiserson, Rivest and Stein, Introduction to Algorithms, section 20.4 (topological sort, with the proof via finishing times).
  • Kahn, "Topological sorting of large networks", Communications of the ACM 5(11), 1962, the queue-and-in-degree algorithm.
  • Sedgewick and Wayne, Algorithms, section 4.2 (directed graphs, DAGs and topological order).
  • Skiena, The Algorithm Design Manual, section 7.10 (topological sorting and its applications).
  • The NeetCode roadmap and the Blind 75, Grind 169, NeetCode 150 and NeetCode 250 lists, used as the index of topics and problems.