Understanding the Problem
In this problem, we are given a directed graph represented as an adjacency list. Our goal is to find all the eventual safe states.
An eventual safe state is a node from which any possible path will always lead to a terminal node (a node with no outgoing edges) and never enter a cycle. If a node can reach a cycle, it is not considered safe.
This means we must find all nodes that do not participate in or lead to a cycle in any way.
Step-by-Step Solution with Example
Step 1: Understand the graph structure
Let’s consider the example: graph = [[1,2],[2,3],[5],[0],[5],[],[]]
Each index represents a node, and the array at each index lists the nodes it points to. For instance, node 0 points to nodes 1 and 2.
Step 2: Reverse the graph
To track which nodes lead to which, we build a reversed graph. In the reversed graph, for every edge u → v in the original graph, we add an edge v → u.
This helps us track who depends on which node, making it easier to determine when a node becomes safe.
Step 3: Track out-degrees
We maintain an array where outdegree[i] stores the number of outgoing edges from node i in the original graph.
Terminal nodes (with no outgoing edges) will have outdegree 0. These are our starting point — they are trivially safe.
Step 4: Use a queue to process safe nodes
We put all terminal nodes in a queue and process them one by one. For each node removed from the queue, we go through its parents in the reversed graph and reduce their out-degree by 1.
If a parent’s out-degree becomes 0, it also becomes safe, so we add it to the queue.
Step 5: Collect and sort safe nodes
All nodes whose out-degree becomes 0 during this process are eventually safe. We collect them, sort them, and return them as the final answer.
Edge Cases
- Empty graph: If the input graph has no nodes, the result should be an empty list.
- All terminal nodes: If every node has no outgoing edges, all are safe.
- All nodes in a cycle: If every node is part of a cycle, no node is safe.
- Disconnected components: The algorithm handles each component independently. Safe nodes from different components are still detected correctly.
Finally
This problem is best understood by thinking in reverse: instead of finding which nodes can reach terminal states, we ask which nodes can be reached from terminal states without forming a cycle.
By reversing the graph and tracking out-degrees, we simulate a safe-state propagation, slowly marking each node safe once all its paths are known to be safe. This approach ensures an intuitive and efficient solution suitable even for large graphs.