802. Find Eventual Safe States #
Problem #
In a directed graph, we start at some node and every turn, walk along a directed edge of the graph. If we reach a node that is terminal (that is, it has no outgoing directed edges), we stop.
Now, say our starting node is eventually safe if and only if we must eventually walk to a terminal node. More specifically, there exists a natural number K so that for any choice of where to walk, we must have stopped at a terminal node in less than K steps.
Which nodes are eventually safe? Return them as an array in sorted order.
The directed graph has N nodes with labels 0, 1, ..., N-1, where N is the length of graph. The graph is given in the following form: graph[i] is a list of labels j such that (i, j) is a directed edge of the graph.
Example:
Input: graph = [[1,2],[2,3],[5],[0],[5],[],[]]
Output: [2,4,5,6]
Here is a diagram of the above graph.

Note:
graphwill have length at most10000.- The number of edges in the graph will not exceed
32000. - Each
graph[i]will be a sorted list of different integers, chosen within the range[0, graph.length - 1].
Problem Summary #
In a directed graph, we start from some node and at each turn walk along a directed edge of the graph. If the node we reach is a terminal node (that is, it has no outgoing directed edges), we stop. Now, if we can eventually walk to a terminal node, then our starting node is eventually safe. More specifically, there exists a natural number K such that no matter where we choose to start walking from, we must stop at a terminal node in fewer than K steps. Which nodes are eventually safe? Return the result as a sorted array.
Notes:
- The number of graph nodes does not exceed 10000.
- The number of edges in the graph will not exceed 32000.
- Each graph[i] is sorted as a list of different integers, chosen within the range [0, graph.length - 1].
Solution Approach #
- Given a directed graph, find all “safe” nodes. The definition of a “safe” node is: there exists a natural number K such that no matter where we choose to start walking from, we must stop at a terminal node in fewer than K steps.
- This problem can be solved using topological sorting, or using DFS coloring. Here we use DFS to solve it. For each node, we have 3 coloring methods: white node 0 indicates that the node has not been visited yet; gray node 1 indicates that the node is in the stack (visited in this round of search) or in a cycle; black node 2 indicates that all adjacent nodes of this node have been visited, and this node is not in a cycle. When we first visit a node, we change it from white to gray and continue searching the nodes connected to it. If during the search we encounter a gray node, it means a cycle has been found. At this point, exit the search, and all gray nodes remain unchanged (that is, starting from any gray node, one can reach the cycle). If during the search we do not encounter a gray node, then when backtracking to the current node, we change it from gray to black, indicating that it is a safe node.
Code #
func eventualSafeNodes(graph [][]int) []int {
res, color := []int{}, make([]int, len(graph))
for i := range graph {
if dfsEventualSafeNodes(graph, i, color) {
res = append(res, i)
}
}
return res
}
// colors: WHITE 0, GRAY 1, BLACK 2;
func dfsEventualSafeNodes(graph [][]int, idx int, color []int) bool {
if color[idx] > 0 {
return color[idx] == 2
}
color[idx] = 1
for i := range graph[idx] {
if !dfsEventualSafeNodes(graph, graph[idx][i], color) {
return false
}
}
color[idx] = 2
return true
}