cconnected.
Understand the pattern. Find your own way.Find your starting point
Your learning feed/Idea 83 of 218
Ideas this builds on
Graphs83 / 218

Find Eventual Safe States

Picture

A node with an exit and a road to a cycle is not safe: one possible walk can loop forever. Begin with terminal nodes, then certify predecessors only after every outgoing choice is safe.

Predict

Why does the algorithm wait for all outgoing choices, rather than any one safe neighbor?

Walk through the small example

The contract says every possible walk must stop. One remaining choice into a cycle is enough to make the starting node unsafe.

Mental model and complete Python solution

Reverse edges so each safe node can notify its original predecessors. remaining[u] counts outgoing edges whose destination has not yet been removed as safe. A terminal starts with zero. Each safe removal decrements the corresponding predecessor counts; reaching zero proves all choices safe. Nodes in or leading to cycles never completely disappear. On a finite graph, an infinite walk must revisit a node and therefore contain a cycle, so this exactly separates safe and unsafe starts.

What the function promises: graph is a finite directed adjacency list. Return all safe node labels in increasing order. Empty and disconnected graphs are allowed.

from collections import deque

def safe_nodes(graph):
    n = len(graph)
    predecessors = [[] for _ in range(n)]
    remaining = [len(neighbors) for neighbors in graph]
    for u, neighbors in enumerate(graph):
        for v in neighbors:
            predecessors[v].append(u)
    ready = deque(u for u in range(n) if remaining[u] == 0)
    safe = [False] * n
    while ready:
        u = ready.popleft()
        safe[u] = True
        for parent in predecessors[u]:
            remaining[parent] -= 1
            if remaining[parent] == 0:
                ready.append(parent)
    return [u for u in range(n) if safe[u]]

assert safe_nodes([[1,2], [], [2]]) == [1]
assert safe_nodes([[1], [2], []]) == [0,1,2]
assert safe_nodes([]) == []

Work and storage: O(V+E) time and space. Producing output by scanning labels avoids an extra sorting cost.

When to recognize it: Terminal nodes are safe. A predecessor becomes safe after all its outgoing choices are known safe.

Check before applying it: One path to an exit is not enough. Another choice might enter a cycle and continue forever.

Change one rule

Now we ask whether there exists at least one path to an exit. How does the rule change?

See what changes

Run ordinary reachability from exits over reversed edges. The first reached safe continuation is enough for this existential question. Requiring all outgoing choices would solve the wrong problem.

Connect

Course Schedule II: Reverse the readiness rule: wait for outgoing choices to disappear.

Strongly connected components: Unsafe nodes are those that can reach a directed cycle.

Problem reference: Find Eventual Safe States (Medium). The walkthrough and Python solution above are original course material.