Find Eventual Safe States

MediumGraphs · Topological Sort🧩 Topological SortLeetCode #802 ↗Updated

Problem

A directed graph with nodes 0..n-1 is given as an adjacency list graph, where graph[i] lists the targets of node i. A node with no outgoing edges is terminal. A node is safe if every walk starting from it is guaranteed to end at a terminal node. Return all safe nodes in ascending order.
Inputgraph = [[1],[2],[],[1,4],[3]]Output[0,1,2]
Inputgraph = [[1,2],[2],[3],[]]Output[0,1,2,3]
Inputgraph = [[0],[0]]Output[]

1 ≤ n ≤ 104  ·  total edges ≤ 4·104  ·  graph[i] sorted, distinct  ·  self-loops allowed

Try it yourself

Java
class Solution {
    public List<Integer> eventualSafeNodes(int[][] graph) {
        
    }
}
Your code is saved in this browser.Submit on LeetCode ↗
Key insight
A node is unsafe exactly when it can reach a cycle. Flip every edge and peel nodes from the terminal end: a node becomes safe once all of its original out-edges lead to already-safe nodes — Kahn's algorithm on out-degrees.
PatternTopological Sort

Instinct Tasks with prerequisites, "X must come before Y", or an order hidden in comparisons: build a directed graph and peel off nodes with zero in-degree.

Memory hack No prereqs? Off you go. Each finished task frees its followers; if someone is never freed, there is a cycle.

Open the Topological Sort guide →

Approaches

Brute ForceFresh Cycle Search From Every Node

Time O(V·(V + E))Space O(V)

For each start node run an independent DFS that tracks which nodes are on the current path. Hitting a node that is already on the path means a cycle is reachable, so the start is unsafe. Nothing is reused between starts.

Java
public List<Integer> eventualSafeNodes(int[][] graph) {
    int n = graph.length;
    List<Integer> safe = new ArrayList<>();
    for (int start = 0; start < n; start++) {
        int[] state = new int[n]; // reset for every start: 0 new, 1 on path, 2 done
        if (!reachesCycle(graph, start, state)) safe.add(start);
    }
    return safe;
}

private boolean reachesCycle(int[][] graph, int u, int[] state) {
    if (state[u] == 1) return true;
    if (state[u] == 2) return false;
    state[u] = 1;
    for (int v : graph[u]) {
        if (reachesCycle(graph, v, state)) return true;
    }
    state[u] = 2;
    return false;
}

Obviously correct, but repeats the same exploration up to V times.

At a glance

LevelApproachTimeSpace
Brute ForceFresh Cycle Search From Every NodeO(V·(V + E))O(V)
BetterMemoised DFS (3-Colour)O(V + E)O(V)
OptimalKahn's Algorithm on the Reversed GraphO(V + E)O(V + E)
Common pitfalls
  • Treating “can reach a terminal” as safe — every path must end.
  • Using plain visited in DFS — cannot tell a cycle from a node already proven safe.
  • Forgetting self-loops ([[0]]): node 0 is on a cycle, so it is unsafe.
Interview tips
  • Restate as “nodes that cannot reach any cycle” — it unlocks both solutions.
  • Explain why grey nodes left grey are exactly the unsafe ones.
  • Offer the reversed-graph Kahn version when the interviewer worries about recursion depth.
Pattern
Reverse topological peeling → classify nodes by whether all their outgoing paths terminate (no reachable cycle).
My notes saved in this browser