802. Find Eventual Safe States - Explanation

Problem Link

Description

The directed graph has nodes numbered from 0 through n-1. Each list graph[i] contains the destinations of outgoing edges from node i. A terminal node has no outgoing edges. A node is safe if every possible sequence of outgoing moves from it eventually ends at a terminal node. Thus a node is unsafe if it can reach a directed cycle, including a self-loop. Return all safe node numbers in increasing order. Terminal nodes themselves are safe.

Example 1:

Find Eventual Safe States, example 1: illustration of the example

Input: graph = [[1],[2],[]]

Output: [0,1,2]

Explanation: Node 2 is terminal. Node 1 can only move to 2, and node 0 can only move through 1 to 2. All three nodes are safe.

Example 2:

Find Eventual Safe States, example 2: illustration of the example

Input: graph = [[1],[0],[]]

Output: [2]

Explanation: Nodes 0 and 1 form a directed cycle, so neither is safe. Node 2 has no outgoing edges and is safe, giving [2].

Example 3:

Find Eventual Safe States, example 3: illustration of the example

Input: graph = [[0,1],[]]

Output: [1]

Explanation: Node 0 can move to terminal node 1, but it can also follow its self-loop forever. Because every possible path must end, node 0 is unsafe. Only node 1 is safe.

Constraints:

  • 1 <= n <= 10000
  • Each adjacency list contains between 0 and n node numbers, sorted in strictly increasing order.
  • Each node number is between 0 and n-1.
  • The total number of directed edges is between 1 and 40000.
  • Self-loops are allowed.


Topics

Company Tags

Please upgrade to NeetCode Pro to view company tags.



Prerequisites

Before attempting this problem, you should be comfortable with:

  • Graph Representation - Understanding adjacency lists and how directed graphs are stored
  • Depth-First Search (DFS) - Traversing graphs and detecting cycles using visited states
  • Memoization - Caching results to avoid redundant computation during graph traversal
  • Topological Sort - Understanding Kahn's algorithm and processing nodes by in-degree/out-degree

Intuition

A node is safe if every path starting from it eventually leads to a terminal node (a node with no outgoing edges). Conversely, a node is unsafe if it can reach a cycle. We can use DFS with memoization to determine safety: initially mark a node as unsafe when we start exploring it (to detect cycles), then mark it safe only if all its neighbors are safe. If we revisit a node marked unsafe during exploration, we have found a cycle.

Algorithm

  1. Create a hash map safe to track each node's safety status.
  2. For each node, run DFS:
    • If the node's status is already known, return it.
    • Mark the node as false (unsafe) initially to detect cycles.
    • Recursively check all neighbors. If any neighbor is unsafe, return false.
    • If all neighbors are safe, mark the current node as true (safe).
  3. Collect all nodes where dfs(node) returns true and return them in order.
class Solution:
    def eventualSafeNodes(self, graph: List[List[int]]) -> List[int]:
        n = len(graph)
        safe = {}

        def dfs(node):
            if node in safe:
                return safe[node]
            safe[node] = False
            for nei in graph[node]:
                if not dfs(nei):
                    return safe[node]
            safe[node] = True
            return safe[node]

        res = []
        for node in range(n):
            if dfs(node):
                res.append(node)
        return res

Time & Space Complexity

  • Time complexity: O(V+E)O(V + E)
  • Space complexity: O(V+E)O(V + E)

Where VV is the number of vertices and EE is the number of edges in the given graph.


2. Topological Sort (Kahn's Algorithm)

Intuition

Think of safe nodes as those that can reach terminal nodes. If we reverse the edge directions, safe nodes are those reachable from terminal nodes. Using Kahn's algorithm on this reversed perspective: terminal nodes have zero out-degree in the original graph. We start from these terminal nodes and work backward, removing edges. Any node whose out-degree reaches zero is safe because all its paths lead to already-confirmed safe nodes.

Algorithm

  1. Build a reverse adjacency list (parents) and track the out-degree of each node.
  2. Add all terminal nodes (out-degree = 0) to a queue.
  3. Process nodes from the queue:
    • For each parent of the current node, decrement its out-degree.
    • If a parent's out-degree becomes 0, add it to the queue.
  4. After processing, all nodes with out-degree 0 are safe.
  5. Return the safe nodes in sorted order.
class Solution:
    def eventualSafeNodes(self, graph: List[List[int]]) -> List[int]:
        n = len(graph)
        outdegree = [0] * n
        parents = [[] for _ in range(n)]
        queue = deque()

        for node in range(n):
            outdegree[node] = len(graph[node])
            if outdegree[node] == 0:
                queue.append(node)
            for nei in graph[node]:
                parents[nei].append(node)

        while queue:
            node = queue.popleft()
            for parent in parents[node]:
                outdegree[parent] -= 1
                if outdegree[parent] == 0:
                    queue.append(parent)

        res = []
        for node in range(n):
            if outdegree[node] <= 0:
                res.append(node)
        return res

Time & Space Complexity

  • Time complexity: O(V+E)O(V + E)
  • Space complexity: O(V+E)O(V + E)

Where VV is the number of vertices and EE is the number of edges in the given graph.


Common Pitfalls

Confusing Visited with Safe Status

A node being visited during DFS does not mean it is safe. You need separate tracking for "currently being explored" (to detect cycles) and "confirmed safe" (all paths lead to terminal nodes). Mixing these states causes incorrect cycle detection or premature safety classification.

Not Returning Results in Sorted Order

The problem requires returning safe nodes in ascending order. If you collect nodes as they are confirmed safe during DFS, the order depends on traversal order, not node indices. Either sort the result at the end or iterate through nodes 0 to n-1 and check each one's safety status.

Incorrectly Handling Nodes with No Outgoing Edges

Terminal nodes (nodes with no outgoing edges) are always safe by definition. In the topological sort approach, these nodes have out-degree 0 and should be the starting points. In DFS, they should immediately return true without further recursion.