[Medium] 802. Find Eventual Safe States
There is a directed graph of n nodes with each node labeled from 0 to n - 1. The graph is represented by a 0-indexed 2D integer array graph where graph[i] is an integer array of nodes adjacent to node i, meaning there is an edge from node i to each node in graph[i].
A node is a terminal node if there are no outgoing edges. A node is a safe node if every possible path starting from that node leads to a terminal node (or another safe node).
Return an array containing all the safe nodes of the graph. The answer should be sorted in ascending order.
Examples
Example 1:
Input: graph = [[1,2],[2,3],[5],[0],[5],[],[]]
Output: [2,4,5,6]
Explanation: The graph is shown above.
Nodes 5 and 6 are terminal nodes, and every path starting at nodes 2, 4, 5, and 6 all lead to either node 5 or 6.
Example 2:
Input: graph = [[1,2,3,4],[1,2],[3,4],[0,4],[]]
Output: [4]
Explanation: Only node 4 is a terminal node, and every path starting at node 4 leads to node 4.
Constraints
n == graph.length1 <= n <= 10^40 <= graph[i].length <= n0 <= graph[i][j] <= n - 1graph[i]is sorted in ascending order.- The graph may contain self-loops.
- The number of edges in the graph will be in the range
[0, n * (n - 1) / 2].
Thinking Process
- Safe Nodes: Nodes that don’t lead to cycles and eventually reach terminal nodes
- Model entities as nodes and relationships as edges.
- Pick traversal (BFS/DFS) or shortest-path (Dijkstra) based on weights.
- Union-Find helps when connectivity updates are frequent.
Common Approaches
Typical techniques for this pattern:
| Approach | Time | Space | Notes |
|---|---|---|---|
| Recursive DFS (this problem) | O(n) | O(h) stack | Natural for trees and graphs |
| Iterative DFS (stack) | O(n) | O(n) | Avoid recursion depth limits |
| DFS with memoization | O(n) | O(n) | Overlapping subproblems on graphs |
| Backtracking DFS | O(2^n) typical | O(n) | Enumerate choices with pruning |
Solution
Solution: DFS with Three-State Coloring
class Solution {
public:
vector<int> eventualSafeNodes(vector<vector<int>>& graph) {
const int N = graph.size();
vector<int> color(N);
vector<int> rtn;
for(int i = 0; i < N; i++) {
if(safe(i, graph, color)) rtn.push_back(i);
}
return rtn;
}
private:
bool safe(int x, vector<vector<int>>& graph, vector<int>& color) {
if(color[x] > 0) {
return color[x] == 2;
}
color[x] = 1;
for(int y: graph[x]) {
if(!safe(y, graph, color)) return false;
}
color[x] = 2;
return true;
}
};
Solution Explanation
Approach: Recursive DFS (this problem)
Key idea: 1. Safe Nodes: Nodes that don’t lead to cycles and eventually reach terminal nodes
How the code works:
- Safe Nodes: Nodes that don’t lead to cycles and eventually reach terminal nodes
- Model entities as nodes and relationships as edges.
- Pick traversal (BFS/DFS) or shortest-path (Dijkstra) based on weights.
- Union-Find helps when connectivity updates are frequent.
Walkthrough — input graph = [[1,2],[2,3],[5],[0],[5],[],[]], expected output [2,4,5,6]:
The graph is shown above. Nodes 5 and 6 are terminal nodes, and every path starting at nodes 2, 4, 5, and 6 all lead to either node 5 or 6.
Algorithm Explanation:
- Initialization (Lines 4-5):
colorarray tracks state:0=unvisited,1=visiting,2=safertnstores result (safe nodes)
- Main Loop (Lines 6-9):
- For each node
i, check if it’s safe using DFS - If safe, add to result
- For each node
- DFS Function
safe()(Lines 12-22):- Base Case (Lines 13-15): If node already visited, return whether it’s safe
- Mark Visiting (Line 16): Set
color[x] = 1to detect cycles - Check Neighbors (Lines 17-19):
- Recursively check all neighbors
- If any neighbor is unsafe (leads to cycle), return
false
- Mark Safe (Line 20): If all neighbors are safe, mark current node as safe
- Return (Line 21): Return
trueif node is safe
How It Works:
- Cycle Detection: If during DFS we encounter a node with
color[x] == 1(visiting), it means we’re in a cycle, so that path is unsafe - Memoization: Once a node is marked as safe (
color[x] == 2), we don’t need to recompute it - Terminal Nodes: Nodes with no outgoing edges are automatically safe (loop doesn’t execute, node marked as safe)
Example Walkthrough:
Input: graph = [[1,2],[2,3],[5],[0],[5],[],[]]
Graph structure:
0 -> 1, 2
1 -> 2, 3
2 -> 5
3 -> 0
4 -> 5
5 -> [] (terminal)
6 -> [] (terminal)
DFS from node 0:
color[0] = 1 (visiting)
Check node 1:
color[1] = 1
Check node 2:
color[2] = 1
Check node 5:
color[5] = 2 (safe, terminal)
color[2] = 2 (safe)
Check node 3:
color[3] = 1
Check node 0:
color[0] == 1 → cycle detected!
Return false
Return false
Return false
Return false
Node 0 is unsafe
DFS from node 2:
color[2] = 1
Check node 5:
color[5] = 2 (safe)
color[2] = 2 (safe)
Node 2 is safe ✓
Result: [2, 4, 5, 6]
Complexity Analysis:
- Time Complexity: O(V + E)
- Each node is visited at most once
- Each edge is traversed at most once
- Overall: O(V + E) where V = number of nodes, E = number of edges
- Space Complexity: O(V)
colorarray: O(V)- Recursion stack: O(V) in worst case
- Result array: O(V)
- Overall: O(V)
Common Mistakes
- All terminal nodes:
graph = [[],[],[]]→ return[0,1,2] - All nodes in cycle:
graph = [[1],[0]]→ return[] - Single node:
graph = [[]]→ return[0] - Disconnected components: Some safe, some unsafe
-
Self-loops: Node pointing to itself is unsafe
- Not detecting cycles correctly: Forgetting to check if node is “visiting”
- Incorrect state transitions: Not marking node as safe after checking neighbors
- Not handling terminal nodes: Terminal nodes should be automatically safe
- Wrong return condition: Returning
falsewhen encountering visiting node - Not sorting result: Result should be sorted in ascending order
Related Problems
- LC 207: Course Schedule - Cycle detection in directed graph
- LC 210: Course Schedule II - Topological sort ordering
- LCR 113: Course Schedule II (CN) - DFS with three-state coloring
- LC 310: Minimum Height Trees - Graph traversal, BFS/DFS
- LC 269: Alien Dictionary - Topological sort
Key Takeaways
- Safe Nodes: Nodes that don’t lead to cycles and eventually reach terminal nodes
- Three-State Coloring: Efficient way to detect cycles and memoize safe nodes
- Terminal Nodes: Automatically safe (no outgoing edges)
- Cycle Detection: If we encounter a “visiting” node during DFS, cycle exists
- Memoization: Once a node is determined safe, reuse the result
References
- LC 802: Find Eventual Safe States on LeetCode
- LeetCode Discuss — LC 802: Find Eventual Safe States
- LeetCode Editorial (may require premium)