medium Find Eventual Safe States
Problem Statement
You are given a directed graph with n nodes, labeled from 0 to n-1. This graph is described by a 2D integer array graph, where graph[i] is an array of nodes adjacent to node i, indicating there is a directed edge from node i to each of the nodes in graph[i].
A node is called a terminal node if it has no outgoing edges. A node is considered safe if every path starting from that node leads to a terminal node (or another safe node).
Return an array of all safe nodes in ascending order.
Examples
Example 1:
- Input: graph =
[[1,2],[2,3],[2],[],[5],[6],[]]
- Expected Output:
[3,4,5,6] - Explanation:
- Node
3is a terminal node. - Node
4leads to node5, which is a safe node. - Node
5leads to node6, which is a terminal node. - Node
6is a terminal node.
- Node
Example 2:
- Input: graph =
[[1,2],[2,3],[5],[0],[],[],[4]]
- Expected Output:
[2,4,5,6] - Explanation:
- Node
2leads to node5, which is a terminal node. - Node
4is a terminal node. - Node
5is a terminal node. - Node
6leads to node4, which is a terminal node.
- Node
Example 3:
- Input: graph =
[[1,2,3],[2,3],[3],[],[0,1,2]]
- Expected Output:
[0,1,2,3,4] - Explanation:
- Node
3is a terminal node. - Node
2leads to node3, which is a terminal node. - Node
1leads to node2, which is a safe node, and node3, which is a terminal node. - Similarly, all node leads to either a terminal or a safe node.
- Node
Constraints:
- n == graph.length
- 1 <= n <= 104
- 0 <= graph[i].length <= n
- 0 <= graph[i][j] <= n - 1
- graph[i] is sorted in a strictly increasing order.
- The graph may contain self-loops.
- The number of edges in the graph will be in the range [1, 4 * 104].
Try it yourself
Try solving this question here:
✅ Solution Find Eventual Safe States
Problem Statement
You are given a directed graph with n nodes, labeled from 0 to n-1. This graph is described by a 2D integer array graph, where graph[i] is an array of nodes adjacent to node i, indicating there is a directed edge from node i to each of the nodes in graph[i].
A node is called a terminal node if it has no outgoing edges. A node is considered safe if every path starting from that node leads to a terminal node (or another safe node).
Return an array of all safe nodes in ascending order.
Examples
Example 1:
- Input: graph =
[[1,2],[2,3],[2],[],[5],[6],[]]
- Expected Output:
[3,4,5,6] - Explanation:
- Node
3is a terminal node. - Node
4leads to node5, which is a safe node. - Node
5leads to node6, which is a terminal node. - Node
6is a terminal node.
- Node
Example 2:
- Input: graph =
[[1,2],[2,3],[5],[0],[],[],[4]]
- Expected Output:
[2,4,5,6] - Explanation:
- Node
2leads to node5, which is a terminal node. - Node
4is a terminal node. - Node
5is a terminal node. - Node
6leads to node4, which is a terminal node.
- Node
Example 3:
- Input: graph =
[[1,2,3],[2,3],[3],[],[0,1,2]]
- Expected Output:
[0,1,2,3,4] - Explanation:
- Node
3is a terminal node. - Node
2leads to node3, which is a terminal node. - Node
1leads to node2, which is a safe node, and node3, which is a terminal node. - Similarly, all node leads to either a terminal or a safe node.
- Node
Constraints:
- n == graph.length
- 1 <= n <= 104
- 0 <= graph[i].length <= n
- 0 <= graph[i][j] <= n - 1
- graph[i] is sorted in a strictly increasing order.
- The graph may contain self-loops.
- The number of edges in the graph will be in the range [1, 4 * 104].
Solution
To solve this problem, we will use a depth-first search (DFS) approach to explore each node and its connections. We'll mark nodes as "safe" or "unsafe" based on whether all paths from the node lead to terminal nodes. This is efficient because we can mark nodes during the DFS traversal and avoid reprocessing them.
This approach works well because we only visit each node a limited number of times, leading to an overall efficient solution. By marking nodes as we determine their status, we avoid unnecessary re-computation. This is particularly useful for graphs, where cycles and repeated paths are common.
Step-by-Step Algorithm
-
Initialize variables:
- Create an array
visitedof sizen(number of nodes) and set all elements to0. This array keeps track of the state of each node:0means unvisited,1means visiting, and-1means safe. - Create an empty list
resultto store the safe nodes.
- Create an array
-
Define a DFS function:
- If the current node is marked as safe (
-1), returnTrue. - If the current node is marked as visiting (
1), returnFalseto indicate a cycle. - Mark the current node as visiting (
1). - Iterate through all adjacent nodes of the current node:
- Recursively call the DFS function on each adjacent node.
- If any adjacent node returns
False, mark the current node as unsafe and returnFalse.
- Mark the current node as safe (
-1). - Return
Trueto indicate the current node is safe.
- If the current node is marked as safe (
-
Process each node:
- Iterate through each node from
0ton-1. - For each node, call the DFS function. If the function returns
True, add the node to theresultlist.
- Iterate through each node from
-
Sort and return the result:
- Sort the
resultlist in ascending order. - Return the
resultlist.
- Sort the
Algorithm Walkthrough
Let's walk through the algorithm step-by-step using the graph [[1,2],[2,3],[2],[],[5],[6],[]]:
Graph representation:
- Node
0-> [1, 2] - Node
1-> [2, 3] - Node
2-> [2] - Node
3-> [] - Node
4-> [5] - Node
5-> [6] - Node
6-> []
Initial state:
visited=[0, 0, 0, 0, 0, 0, 0]result=[]
Step-by-step DFS calls:
-
Processing node
0:DFS(0)is called.visited[0]is marked as1.- Processing adjacent node
1of node0:DFS(1)is called.visited[1]is marked as1.- Processing adjacent node
2of node1:DFS(2)is called.visited[2]is marked as1.- Processing adjacent node
2of node2:DFS(2)is called.visited[2]is already1(cycle detected).- Return
False.
visited[2]remains1(unsafe).- Return
False.
visited[1]remains1(unsafe).- Return
False.
visited[0]remains1(unsafe).- Return
False.
-
Processing node
1:DFS(1)is called.visited[1]is already1(unsafe).- Return
False.
-
Processing node
2:DFS(2)is called.visited[2]is already1(unsafe).- Return
False.
-
Processing node
3:DFS(3)is called.visited[3]is marked as1.- Node
3has no adjacent nodes. visited[3]is marked as-1(safe).- Add node
3toresult. result=[3]- Return
True.
-
Processing node
4:DFS(4)is called.visited[4]is marked as1.- Processing adjacent node
5of node4:DFS(5)is called.visited[5]is marked as1.- Processing adjacent node
6of node5:DFS(6)is called.visited[6]is marked as1.- Node
6has no adjacent nodes. visited[6]is marked as-1(safe).- Add node
6toresult. result=[3, 6]- Return
True.
visited[5]is marked as-1(safe).- Add node
5toresult. result=[3, 6, 5]- Return
True.
visited[4]is marked as-1(safe).- Add node
4toresult. result=[3, 6, 5, 4]- Return
True.
-
Processing node
5:DFS(5)is called.visited[5]is already-1(safe).- Return
True.
-
Processing node
6:DFS(6)is called.visited[6]is already-1(safe).- Return
True.
Final result:
- Sort
result=[3, 4, 5, 6] - Return
[3, 4, 5, 6]
Code
import java.util.*;
public class Solution {
public List<Integer> eventualSafeNodes(int[][] graph) {
int n = graph.length;
int[] visited = new int[n]; // 0: unvisited, 1: visiting, -1: safe
List<Integer> result = new ArrayList<>();
for (int i = 0; i < n; i++) {
if (dfs(graph, visited, i)) {
result.add(i);
}
}
Collections.sort(result); // Sorting the result
return result;
}
private boolean dfs(int[][] graph, int[] visited, int node) {
if (visited[node] == -1) return true; // If node is marked as safe
if (visited[node] == 1) return false; // If node is part of a cycle
visited[node] = 1; // Mark the node as visiting
for (int next : graph[node]) {
if (!dfs(graph, visited, next)) {
return false; // If any adjacent node is not safe
}
}
visited[node] = -1; // Mark the node as safe
return true;
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(
sol.eventualSafeNodes(
new int[][] { { 1, 2 }, { 2, 3 }, { 2 }, {}, { 5 }, { 6 }, {} }
)
);
System.out.println(
sol.eventualSafeNodes(
new int[][] { { 1, 2 }, { 2, 3 }, { 5 }, { 0 }, {}, {}, { 4 } }
)
);
System.out.println(
sol.eventualSafeNodes(
new int[][] { { 1, 2, 3 }, { 2, 3 }, { 3 }, {}, { 0, 1, 2 } }
)
);
}
}
Complexity Analysis
Time Complexity
- The time complexity of the algorithm is
, where V is the number of vertices (nodes) and E is the number of edges in the graph. This is because each node and each edge are processed once in the depth-first search (DFS).
Space Complexity
- The space complexity of the algorithm is
, where V is the number of vertices. This is due to the space required to store the visitedarray and the recursion stack during the DFS.
🤖 Don't fully get this? Learn it with Claude
Stuck on Find Eventual Safe States? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.
Progressively stronger hints — you still solve it.
I'm working on the problem **Find Eventual Safe States** (DSA). Give me a HINT LADDER: start with the tiniest nudge, then wait. Only reveal the next, stronger hint when I ask. Do NOT show the full solution unless I type 'show solution'. Keep me doing the thinking. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
See the technique, not just code.
Explain the optimal approach to **Find Eventual Safe States** with a VISUAL walkthrough: trace it on a small concrete example using ASCII art / a step-by-step diagram, narrate what changes each step, then give time & space complexity with a one-line derivation. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Catch bugs, edge cases, sub-optimality.
I'll paste my solution to **Find Eventual Safe States**. Review it for correctness, missed edge cases, and time/space complexity, then coach me toward the optimal — don't just rewrite it. Ask me to paste my code now. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Lock in recognition with look-alikes.
Give me 2 problems that use the SAME underlying pattern as **Find Eventual Safe States**. For each, let me attempt first, then review my answer and name the trigger signal that reveals the pattern. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.