class Solution:
def eventualSafeNodes(self, graph):
n = len(graph)
color = [0] * n # 0=unvisited, 1=visiting, 2=safe
def dfs(node):
if color[node] != 0:
return color[node] == 2
color[node] = 1
for nei in graph[node]:
if color[nei] == 2:
continue
if color[nei] == 1 or not dfs(nei):
return False
color[node] = 2
return True
return [i for i in range(n) if dfs(i)]
class Solution {
public:
vector<int> eventualSafeNodes(vector<vector<int>>& graph) {
int n = graph.size();
vector<int> color(n, 0); // 0=unvisited, 1=visiting, 2=safe
vector<int> res;
function<bool(int)> dfs = [&](int node) {
if (color[node] != 0) return color[node] == 2;
color[node] = 1;
for (int nei : graph[node]) {
if (color[nei] == 2) continue;
if (color[nei] == 1 || !dfs(nei)) return false;
}
color[node] = 2;
return true;
};
for (int i = 0; i < n; ++i)
if (dfs(i)) res.push_back(i);
return res;
}
};
class Solution {
public List<Integer> eventualSafeNodes(int[][] graph) {
int n = graph.length;
int[] color = new int[n]; // 0=unvisited, 1=visiting, 2=safe
List<Integer> res = new ArrayList<>();
for (int i = 0; i < n; ++i) {
if (dfs(graph, color, i)) res.add(i);
}
return res;
}
private boolean dfs(int[][] graph, int[] color, int node) {
if (color[node] != 0) return color[node] == 2;
color[node] = 1;
for (int nei : graph[node]) {
if (color[nei] == 2) continue;
if (color[nei] == 1 || !dfs(graph, color, nei)) return false;
}
color[node] = 2;
return true;
}
}
var eventualSafeNodes = function(graph) {
const n = graph.length;
const color = new Array(n).fill(0); // 0=unvisited, 1=visiting, 2=safe
function dfs(node) {
if (color[node] !== 0) return color[node] === 2;
color[node] = 1;
for (const nei of graph[node]) {
if (color[nei] === 2) continue;
if (color[nei] === 1 || !dfs(nei)) return false;
}
color[node] = 2;
return true;
}
const res = [];
for (let i = 0; i < n; ++i) {
if (dfs(i)) res.push(i);
}
return res;
};
You are given a directed graph represented as an adjacency list, where graph[i]
is a list of nodes that node i
points to. A node is called eventually safe if, starting from that node, every possible path leads to a terminal node (a node with no outgoing edges) or another safe node, and never leads to a cycle.
Your task is to return a list of all the eventual safe nodes in ascending order.
First, let's understand what makes a node "safe": it must not be possible to start from that node and get stuck in a cycle, no matter which path you take. If a node leads only to terminal nodes (or to other safe nodes), it's safe. If it can reach a cycle, it's not safe.
A brute-force approach would be to try all paths starting from each node and see if any path leads to a cycle. But this is inefficient because there can be exponential numbers of paths.
To optimize, we need a way to avoid redundant work and quickly detect cycles. We can use depth-first search (DFS) with memoization (caching results) and a way to mark nodes as "visiting" to detect cycles during DFS. If we revisit a "visiting" node, we've found a cycle. If we finish exploring a node and find all its paths are safe, we mark it as safe.
We use a DFS-based coloring algorithm to classify nodes:
color[node] == 0
), start a DFS from that node.color[node] = 1
).color[nei] == 2
), continue.color[node] = 2
) and return true.This approach ensures that each node is processed at most once, and cycles are detected efficiently.
Sample Input: graph = [[1,2],[2,3],[5],[0],[5],[],[]]
Graph:
[2, 4, 5, 6]
O(2^N)
in the worst case.
O(N + E)
, where N
is the number of nodes and E
is the number of edges. Each node and edge is visited at most once.O(N)
for the color/state array and call stack (in the worst case of a linear graph).The "Find Eventual Safe States" problem asks us to identify all nodes in a directed graph that cannot reach a cycle, no matter which path is chosen. By using DFS with a coloring scheme, we efficiently detect cycles and avoid redundant computation. This approach is both intuitive and optimal for sparse and dense graphs, making it a robust solution for this class of problems. The key insight is to cache results and use cycle detection during DFS, which brings the complexity down to linear in the size of the graph.