Pattern #50

Graph DFS

Important interview questions, thinking patterns, Call Stack Deep Exploration, Directed 3-State Cycle Detection, Permanent vs Backtracking Unmarking, Grid DFS, and Go templates.

Must Solve

20 core questions — solve these first.

  1. 1.Number of Islands
    medium
  2. 2.Clone Graph
    medium
  3. 3.Number of Provinces
    medium
  4. 4.Max Area of Island
    medium
  5. 5.Flood Fill
    easy
  6. 6.Surrounded Regions
    medium
  7. 7.Pacific Atlantic Water Flow
    medium
  8. 8.Course Schedule
    medium
  9. 9.Graph Valid Tree
    medium
  10. 10.Is Graph Bipartite?
    medium
  11. 11.Keys and Rooms
    medium
  12. 12.All Paths From Source to Target
    medium
  13. 13.Reorder Routes to Make All Paths Lead to Zero
    medium
  14. 14.Evaluate Division
    medium
  15. 15.Detonate the Maximum Bombs
    medium
  16. 16.Find Eventual Safe States
    medium
  17. 17.Possible Bipartition
    medium
  18. 18.Accounts Merge
    medium
  19. 19.Redundant Connection
    medium
  20. 20.Number of Connected Components
    medium

Also Important

10 more questions worth practicing.

  1. 21.Path With Maximum Gold
    medium
  2. 22.Word Search
    medium
  3. 23.Count Sub Islands
    medium
  4. 24.Closed Islands
    medium
  5. 25.Number of Enclaves
    medium
  6. 26.Coloring a Border
    medium
  7. 27.Find if Path Exists in Graph
    easy
  8. 28.Minimum Height Trees
    medium
  9. 29.Longest Increasing Path in a Matrix
    hard
  10. 30.Critical Connections in a Network
    hard

How to Think

  1. Need explore one connected region?DFS (Go deep along paths)
  2. Need count connected groups?DFS + Visited Set
  3. Need detect cycles?DFS + 3-State Array (0=unvisited, 1=visiting, 2=finished)
  4. Need explore every possible path?DFS + Backtracking (Unmark upon return)
  5. Matrix has connected cells?Grid DFS (4-directional offsets)

Go Graph DFS Code Templates

Standard Graph DFS, 3-State Directed Cycle DFS & Grid DFS in Go

// 1. Standard Graph DFS (Connected Components): O(V + E) Time
func graphDFS(n int, adj [][]int) int {
    visited := make([]bool, n)
    components := 0

    var dfs func(u int)
    dfs = func(u int) {
        visited[u] = true // Mark immediately upon entry!
        for _, neighbor := range adj[u] {
            if !visited[neighbor] {
                dfs(neighbor)
            }
        }
    }

    for i := 0; i < n; i++ {
        if !visited[i] {
            components++
            dfs(i)
        }
    }
    return components
}

// 2. Directed Cycle Detection (Course Schedule 3-State DFS)
// States: 0 = Unvisited, 1 = Visiting (active DFS path), 2 = Finished
func canFinish(numCourses int, prerequisites [][]int) bool {
    adj := make([][]int, numCourses)
    for _, p := range prerequisites {
        adj[p[1]] = append(adj[p[1]], p[0])
    }

    state := make([]int, numCourses)

    var hasCycle func(u int) bool
    hasCycle = func(u int) bool {
        if state[u] == 1 { return true }  // Found node in active DFS path -> Cycle!
        if state[u] == 2 { return false } // Fully processed node -> Safe!

        state[u] = 1 // Mark visiting
        for _, neighbor := range adj[u] {
            if hasCycle(neighbor) {
                return true
            }
        }
        state[u] = 2 // Mark finished
        return false
    }

    for i := 0; i < numCourses; i++ {
        if state[i] == 0 && hasCycle(i) {
            return false // Cycle found -> Cannot finish all courses!
        }
    }
    return true
}

// 3. Grid DFS (Number of Islands / Max Area): O(R · C) Time
func numIslands(grid [][]byte) int {
    rows, cols := len(grid), len(grid[0])
    islands := 0

    var dfs func(r, c int)
    dfs = func(r, c int) {
        if r < 0 || r >= rows || c < 0 || c >= cols || grid[r][c] != '1' {
            return
        }
        grid[r][c] = '0' // Sink island / mark visited!
        dfs(r-1, c); dfs(r+1, c); dfs(r, c-1); dfs(r, c+1)
    }

    for r := 0; r < rows; r++ {
        for c := 0; c < cols; c++ {
            if grid[r][c] == '1' {
                islands++
                dfs(r, c)
            }
        }
    }
    return islands
}

👉 Total Time: O(V + E) / O(R · C) | Stack Space: O(V) / O(R · C)

Cycle Detection States: Undirected vs Directed

1. Undirected Cycle Detection: Pass the parent node to the DFS call. If a neighbor is already visited AND neighbor != parent, a cycle exists!

2. Directed Cycle Detection (3-State Array): A simple boolean visited array is NOT enough for directed graphs! Use a 3-state array: 0 = unvisited, 1 = visiting (active in current call stack), 2 = finished. Reaching state 1 implies a back-edge directed cycle.

Permanent Visited vs Path Backtracking Unmarking

1. Permanent Visited (Component DFS): When counting connected components or island cells, mark visited[u] = true permanently. Do NOT unmark because connected nodes belong to the same component.

2. Temporary Visited (Path Backtracking): When exploring all paths (All Paths From Source to Target, Word Search), mark visited[u] = true before recursing, then UNMARK visited[u] = false after returning so the node can be reused in alternative paths!

Visual Memory Rule
Graph DFS        → Go deep along paths using call stack LIFO structure
Visited Set      → MANDATORY! Mark immediately upon entry (visited[u] = true)
Component Count  → Outer loop over all nodes; new DFS trigger = 1 new component
Directed Cycle   → 3-State Array (0=unvisited, 1=visiting, 2=finished)
Backtracking     → Mark -> Recurse -> UNMARK (visited[u] = false)

💡 Golden Rule: "Mark where you have been, follow one path as deep as possible, and only return when that path has nothing new left to explore."

Common Interview Mistakes

1. Forgetting Visited Set

Omitting the visited set in a graph with cycles causes infinite recursion and stack overflow crashes!

2. Using 2-State Boolean Array for Directed Cycles

In directed graphs, seeing a visited node from a previous independent branch is NOT a cycle! You must use a 3-state array (0, 1, 2).

3. Forgetting Parent Check in Undirected Cycle

In undirected graphs, the node you came from (parent) is already visited. Check neighbor != parent before declaring a cycle.

4. Permanently Marking During Path Backtracking

When enumerating all paths (Word Search, Path Max Gold), you MUST unmark visited[u] = false upon returning.

Interview Rules

  1. 1. Explore connected region / group? → Graph DFS
  2. 2. When to mark visited? → Mark visited[u] = true immediately upon entry
  3. 3. Count connected components?→ Outer loop over all nodes; if unvisited, increment count & trigger DFS
  4. 4. Directed Cycle Detection (Course Schedule)? → 3-State Array (0=unvisited, 1=visiting, 2=finished)
  5. 5. Undirected Cycle Detection? → Check visited[neighbor] && neighbor != parent
  6. 6. Explore all paths? → DFS + Backtracking (unmark node upon return)
  7. 7. Repeated subproblem work on Matrix/Graph? → DFS + Memoization (memo[r][c])
  8. 8. Overall Complexity → Time: O(V + E) | Stack Space: O(V)

Small Rules

  1. Rule 1: DFS goes deep along paths using call stack LIFO ordering.
  2. Rule 2: Mark nodes as visited immediately upon entering the function.
  3. Rule 3: 3-state tracking is required for directed graph cycle detection.
  4. Rule 4: Component DFS marks visited permanently; Backtracking unmarks nodes upon return.
  5. Rule 5: Grid DFS transforms 2D cell matrices into connected components.

Production Thinking

Microservice Dependency InspectionTraversing full downstream microservice call graphs for impact analysis

Build System Cycle DetectionDetecting circular package dependencies using 3-state DFS prior to compilation

Network Component ReachabilityDiscovering all machines reachable from a compromised security gateway

File System Reference TreesRecursively inspecting nested folder dependencies with cycle protection

Remember This

Graph DFS        → Go Deep
Visited          → Prevent cycles & repeats
Basic Pattern    → Mark -> Explore neighbors
Components       → New DFS = New Group
Grid             → Cell = Node
All Paths        → DFS + Backtracking
Undirected Cycle → Parent check
Directed Cycle   → 3-State (0=unvisited, 1=visiting, 2=finished)
Clone Graph      → DFS + Map
Repeated States  → DFS + Memo
Time             → O(V + E)

💡 Golden Rule: "Mark where you have been, follow one path as deep as possible, and only return when that path has nothing new left to explore."