🧠
Brute Force (DFS Cycle Detection)
💡 This approach exists to build intuition about cycles and node safety by directly exploring all paths. It is slow but helps understand why cycles make nodes unsafe.
Intuition
We try to detect if starting from a node, we can reach a cycle. If yes, the node is unsafe; otherwise, it is safe.
Algorithm
- Initialize arrays to track visited nodes and recursion stack.
- For each node, run DFS to detect cycles.
- If a cycle is detected starting from a node, mark it unsafe.
- Collect all nodes that are not part of any cycle as safe nodes.
💡 The recursion stack helps detect cycles by tracking nodes currently being explored, which is key to identifying unsafe nodes.
from typing import List
def eventualSafeNodes(graph: List[List[int]]) -> List[int]:
n = len(graph)
visited = [False] * n
onPath = [False] * n
safe = [False] * n
def dfs(node: int) -> bool:
if visited[node]:
return safe[node]
visited[node] = True
onPath[node] = True
for nei in graph[node]:
if onPath[nei] or not dfs(nei):
return False
onPath[node] = False
safe[node] = True
return True
return [i for i in range(n) if dfs(i)]
# Driver code
if __name__ == '__main__':
graph = [[1,2],[2,3],[5],[0],[5],[],[]]
print(eventualSafeNodes(graph))
Line Notes
visited = [False] * nTrack if node has been visited to avoid repeated work
onPath = [False] * nTrack nodes currently in recursion stack to detect cycles
if visited[node]:If already visited, return previously computed safety
for nei in graph[node]:Explore all neighbors to check for cycles
import java.util.*;
public class Solution {
public List<Integer> eventualSafeNodes(int[][] graph) {
int n = graph.length;
boolean[] visited = new boolean[n];
boolean[] onPath = new boolean[n];
boolean[] safe = new boolean[n];
List<Integer> res = new ArrayList<>();
for (int i = 0; i < n; i++) {
if (dfs(i, graph, visited, onPath, safe)) {
res.add(i);
}
}
return res;
}
private boolean dfs(int node, int[][] graph, boolean[] visited, boolean[] onPath, boolean[] safe) {
if (visited[node]) return safe[node];
visited[node] = true;
onPath[node] = true;
for (int nei : graph[node]) {
if (onPath[nei] || !dfs(nei, graph, visited, onPath, safe)) {
return false;
}
}
onPath[node] = false;
safe[node] = true;
return true;
}
// Main method for testing
public static void main(String[] args) {
Solution sol = new Solution();
int[][] graph = {{1,2},{2,3},{5},{0},{5},{}, {}};
System.out.println(sol.eventualSafeNodes(graph));
}
}
Line Notes
boolean[] visited = new boolean[n];Track visited nodes to avoid recomputation
boolean[] onPath = new boolean[n];Detect cycles by tracking recursion stack
if (visited[node]) return safe[node];Return cached safety result if visited
for (int nei : graph[node])Check all neighbors for cycles
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
vector<int> eventualSafeNodes(vector<vector<int>>& graph) {
int n = graph.size();
vector<bool> visited(n, false), onPath(n, false), safe(n, false);
vector<int> res;
for (int i = 0; i < n; i++) {
if (dfs(i, graph, visited, onPath, safe)) {
res.push_back(i);
}
}
return res;
}
private:
bool dfs(int node, vector<vector<int>>& graph, vector<bool>& visited, vector<bool>& onPath, vector<bool>& safe) {
if (visited[node]) return safe[node];
visited[node] = true;
onPath[node] = true;
for (int nei : graph[node]) {
if (onPath[nei] || !dfs(nei, graph, visited, onPath, safe)) {
return false;
}
}
onPath[node] = false;
safe[node] = true;
return true;
}
};
int main() {
Solution sol;
vector<vector<int>> graph = {{1,2},{2,3},{5},{0},{5},{}, {}};
vector<int> safeNodes = sol.eventualSafeNodes(graph);
for (int node : safeNodes) cout << node << ' ';
cout << endl;
return 0;
}
Line Notes
vector<bool> visited(n, false)Initialize visited array to track explored nodes
vector<bool> onPath(n, false)Track recursion stack to detect cycles
if (visited[node]) return safe[node];Use memoized result if node already processed
for (int nei : graph[node])Explore all neighbors to detect cycles
var eventualSafeNodes = function(graph) {
const n = graph.length;
const visited = new Array(n).fill(false);
const onPath = new Array(n).fill(false);
const safe = new Array(n).fill(false);
function dfs(node) {
if (visited[node]) return safe[node];
visited[node] = true;
onPath[node] = true;
for (const nei of graph[node]) {
if (onPath[nei] || !dfs(nei)) return false;
}
onPath[node] = false;
safe[node] = true;
return true;
}
const res = [];
for (let i = 0; i < n; i++) {
if (dfs(i)) res.push(i);
}
return res;
};
// Test
console.log(eventualSafeNodes([[1,2],[2,3],[5],[0],[5],[],[]]));
Line Notes
const visited = new Array(n).fill(false);Track nodes already visited to avoid repeated DFS
const onPath = new Array(n).fill(false);Detect cycles by tracking recursion stack
if (visited[node]) return safe[node];Return cached safety result if node processed
for (const nei of graph[node])Check all neighbors for cycles
Each node and edge is visited once during DFS. The recursion stack and visited arrays use O(V) space.
💡 For n=1000 nodes and m=2000 edges, this means roughly 3000 operations, which is efficient enough for medium-sized graphs.
Interview Verdict: Accepted but can be slow on very large graphs
This approach is intuitive and correct but may cause stack overflow or timeout on very large inputs due to deep recursion.