🧠
DFS with Coloring (Three States) to Detect Cycles
💡 This approach improves cycle detection clarity and efficiency by using three states (unvisited, visiting, visited) instead of two sets, reducing overhead and making the logic explicit.
Intuition
Mark nodes as unvisited (0), visiting (1), or visited (2). If during DFS you reach a node marked visiting, a cycle exists.
Algorithm
- Build adjacency list from prerequisites.
- Initialize all nodes as unvisited (0).
- For each node, if unvisited, perform DFS.
- Mark node as visiting (1) when DFS starts.
- If a neighbor is visiting, cycle detected.
- After DFS, mark node as visited (2).
- Return false if cycle found, else true.
💡 The three states help differentiate nodes currently in the recursion stack from those fully processed, simplifying cycle detection.
from typing import List
def canFinish(numCourses: int, prerequisites: List[List[int]]) -> bool:
graph = [[] for _ in range(numCourses)]
for dest, src in prerequisites:
graph[src].append(dest)
state = [0] * numCourses # 0=unvisited,1=visiting,2=visited
def dfs(node):
if state[node] == 1:
return False
if state[node] == 2:
return True
state[node] = 1
for neighbor in graph[node]:
if not dfs(neighbor):
return False
state[node] = 2
return True
for course in range(numCourses):
if state[course] == 0:
if not dfs(course):
return False
return True
# Driver code
if __name__ == '__main__':
print(canFinish(2, [[1,0]])) # True
print(canFinish(2, [[1,0],[0,1]])) # False
Line Notes
state = [0] * numCoursesInitialize all nodes as unvisited
if state[node] == 1:Cycle detected if node is currently being visited
if state[node] == 2:Skip nodes already fully visited
state[node] = 1Mark node as visiting when DFS starts
for neighbor in graph[node]:Traverse neighbors recursively
state[node] = 2Mark node as visited after DFS completes
for course in range(numCourses):Check all courses for cycles
if not dfs(course):Return false if cycle detected
import java.util.*;
public class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
List<List<Integer>> graph = new ArrayList<>();
for (int i = 0; i < numCourses; i++) {
graph.add(new ArrayList<>());
}
for (int[] pair : prerequisites) {
graph.get(pair[1]).add(pair[0]);
}
int[] state = new int[numCourses]; // 0=unvisited,1=visiting,2=visited
for (int course = 0; course < numCourses; course++) {
if (state[course] == 0) {
if (!dfs(course, graph, state)) {
return false;
}
}
}
return true;
}
private boolean dfs(int node, List<List<Integer>> graph, int[] state) {
if (state[node] == 1) return false;
if (state[node] == 2) return true;
state[node] = 1;
for (int neighbor : graph.get(node)) {
if (!dfs(neighbor, graph, state)) {
return false;
}
}
state[node] = 2;
return true;
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.canFinish(2, new int[][]{{1,0}})); // true
System.out.println(sol.canFinish(2, new int[][]{{1,0},{0,1}})); // false
}
}
Line Notes
int[] state = new int[numCourses];Initialize all nodes as unvisited
if (state[node] == 1) return false;Cycle detected if node is currently visiting
if (state[node] == 2) return true;Skip nodes already visited
state[node] = 1;Mark node as visiting
for (int neighbor : graph.get(node))Traverse neighbors recursively
state[node] = 2;Mark node as visited after DFS
for (int course = 0; course < numCourses; course++)Check all courses for cycles
if (!dfs(course, graph, state))Return false if cycle detected
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
vector<vector<int>> graph(numCourses);
for (auto& p : prerequisites) {
graph[p[1]].push_back(p[0]);
}
vector<int> state(numCourses, 0); // 0=unvisited,1=visiting,2=visited
for (int course = 0; course < numCourses; ++course) {
if (state[course] == 0) {
if (!dfs(course, graph, state)) {
return false;
}
}
}
return true;
}
private:
bool dfs(int node, vector<vector<int>>& graph, vector<int>& state) {
if (state[node] == 1) return false;
if (state[node] == 2) return true;
state[node] = 1;
for (int neighbor : graph[node]) {
if (!dfs(neighbor, graph, state)) {
return false;
}
}
state[node] = 2;
return true;
}
};
int main() {
Solution sol;
vector<vector<int>> prereq1 = {{1,0}};
cout << boolalpha << sol.canFinish(2, prereq1) << endl; // true
vector<vector<int>> prereq2 = {{1,0},{0,1}};
cout << boolalpha << sol.canFinish(2, prereq2) << endl; // false
return 0;
}
Line Notes
vector<int> state(numCourses, 0);Initialize all nodes as unvisited
if (state[node] == 1) return false;Cycle detected if node is visiting
if (state[node] == 2) return true;Skip nodes already visited
state[node] = 1;Mark node as visiting
for (int neighbor : graph[node])Traverse neighbors recursively
state[node] = 2;Mark node as visited after DFS
for (int course = 0; course < numCourses; ++course)Check all courses for cycles
if (!dfs(course, graph, state))Return false if cycle detected
function canFinish(numCourses, prerequisites) {
const graph = Array.from({length: numCourses}, () => []);
for (const [dest, src] of prerequisites) {
graph[src].push(dest);
}
const state = new Array(numCourses).fill(0); // 0=unvisited,1=visiting,2=visited
function dfs(node) {
if (state[node] === 1) return false;
if (state[node] === 2) return true;
state[node] = 1;
for (const neighbor of graph[node]) {
if (!dfs(neighbor)) return false;
}
state[node] = 2;
return true;
}
for (let course = 0; course < numCourses; course++) {
if (state[course] === 0) {
if (!dfs(course)) return false;
}
}
return true;
}
// Test cases
console.log(canFinish(2, [[1,0]])); // true
console.log(canFinish(2, [[1,0],[0,1]])); // false
Line Notes
const state = new Array(numCourses).fill(0);Initialize all nodes as unvisited
if (state[node] === 1) return false;Cycle detected if node is visiting
if (state[node] === 2) return true;Skip nodes already visited
state[node] = 1;Mark node as visiting
for (const neighbor of graph[node])Traverse neighbors recursively
state[node] = 2;Mark node as visited after DFS
for (let course = 0; course < numCourses; course++)Check all courses for cycles
if (!dfs(course)) return false;Return false if cycle detected
TimeO(V + E)
SpaceO(V + E)
Each node and edge is visited once. The state array uses O(V) space.
💡 This approach is more efficient and clearer than the brute force recursion stack method, making it better for large inputs.
Interview Verdict: Accepted and preferred over brute force due to clarity and efficiency.
This is the standard DFS cycle detection approach in interviews for directed graphs.