BFS and DFS — The Two Traversals That Power Everything

Every graph algorithm you will learn in this phase is BFS or DFS with one additional idea layered on top. Dijkstra is BFS on a priority queue. Topological sort is DFS with a finish-time stack. Cycle detection is DFS with a color scheme. If you own BFS and DFS deeply — their mechanics, their invariants, their failure modes — every other algorithm becomes a variation rather than something new to memorize.

This file is the longest in Phase 3. That reflects the importance. Spend proportional time here.




DFS Applications

1. Cycle Detection in Undirected Graphs

A cycle exists if DFS visits a node that is already visited AND is not the direct parent of the current node.

boolean hasCycleUndirected(int node, int parent, boolean[] visited, List<List<Integer>> adj) {
    visited[node] = true;

    for (int neighbor : adj.get(node)) {
        if (!visited[neighbor]) {
            if (hasCycleUndirected(neighbor, node, visited, adj)) return true;
        } else if (neighbor != parent) {
            return true;    // visited neighbor that isn't our parent = back edge = cycle
        }
    }
    return false;
}

What most people get wrong: using this undirected technique for directed graphs. In a directed graph, reaching a visited node does NOT mean a cycle exists — it may just be a cross edge from one branch to another. Directed graph cycle detection requires three-color DFS (see below).


2. Cycle Detection in Directed Graphs (Three-Color DFS)

Three states per node:

  • White (0): unvisited

  • Gray (1): currently on the DFS path (in the call stack)

  • Black (2): fully processed (all descendants explored)

A cycle exists if DFS reaches a gray node. Reaching a black node is fine — it’s just a cross edge.

int[] color;   // 0=white, 1=gray, 2=black

boolean hasCycleDirected(int node) {
    color[node] = 1;    // mark gray (in progress)

    for (int neighbor : adj.get(node)) {
        if (color[neighbor] == 1) return true;    // back edge to gray = cycle
        if (color[neighbor] == 0) {               // white = unvisited
            if (hasCycleDirected(neighbor)) return true;
        }
        // black: already done, skip
    }

    color[node] = 2;    // mark black (done)
    return false;
}

// Call from main:
color = new int[V];  // all white initially
for (int i = 0; i < V; i++)
    if (color[i] == 0)
        if (hasCycleDirected(i)) return true;

3. Connected Components (Undirected)

Already established in fundamentals: loop over all vertices, trigger DFS/BFS for each unvisited one.

int countComponents(int V, int[][] edges) {
    List<List<Integer>> adj = buildAdj(V, edges);
    boolean[] visited = new boolean[V];
    int count = 0;

    for (int i = 0; i < V; i++) {
        if (!visited[i]) {
            count++;
            dfs(i, visited, adj);
        }
    }
    return count;
}

4. Flood Fill (LC 733)

Grid BFS/DFS from a starting cell, changing the color of all connected cells with the same original color. This is just connected component detection on a grid.

void floodFill(int[][] image, int sr, int sc, int newColor) {
    int originalColor = image[sr][sc];
    if (originalColor == newColor) return;   // avoid infinite loop
    dfs(image, sr, sc, originalColor, newColor);
}

void dfs(int[][] image, int r, int c, int orig, int newColor) {
    if (r < 0 || r >= image.length || c < 0 || c >= image[0].length) return;
    if (image[r][c] != orig) return;

    image[r][c] = newColor;    // mark visited by changing color
    dfs(image, r+1, c, orig, newColor);
    dfs(image, r-1, c, orig, newColor);
    dfs(image, r, c+1, orig, newColor);
    dfs(image, r, c-1, orig, newColor);
}

Using the grid value itself as the visited marker (changing to newColor) avoids a separate visited array. Only valid when the new value is distinguishable from the old.


BFS vs DFS: When to Use Which

Need

Use

Why

Shortest path (unweighted)

BFS

Level invariant guarantees minimum hops

Cycle detection (undirected)

Either

Both work

Cycle detection (directed)

DFS (3-color)

BFS doesn’t naturally produce back-edge detection

Topological sort

DFS or BFS (Kahn’s)

DFS gives finish-time order; Kahn’s is BFS on in-degree

Connected components

Either

Both work equally well

Path existence

Either

DFS is simpler; BFS finds shortest

Level/layer information

BFS

Level structure is natural in BFS

Maze / flood fill

Either

DFS is simpler; BFS gives minimum path


Practice Problems

#

Problem

Difficulty

Focus

1

LC 200 — Number of Islands

Medium

BFS/DFS on grid, connected components

2

LC 994 — Rotting Oranges

Medium

Multi-source BFS, level timing

3

LC 542 — 01 Matrix

Medium

Multi-source BFS from all 0s simultaneously

4

LC 695 — Max Area of Island

Medium

DFS with area accumulation

5

LC 127 — Word Ladder

Hard

BFS shortest path in implicit graph

6

LC 207 — Course Schedule

Medium

DFS cycle detection in directed graph

7

LC 417 — Pacific Atlantic Water Flow

Medium

Multi-source BFS from two borders

8

LC 130 — Surrounded Regions

Medium

BFS from border, mark safe cells

Do problems 1, 2, 3 first — they establish BFS fluency. Then 4 and 6 for DFS. Problem 5 (Word Ladder) is the payoff problem: each word is a node, edges connect words differing by one character. The graph is never explicitly built — you construct neighbors on the fly during BFS.