Skip to content

Depth-First Search (DFS)

Definition

A graph traversal algorithm that explores as far as possible along each branch before backtracking. Uses a STACK (explicit or call stack).

Key Properties: - Explores depth before breadth - Uses Stack (recursion or explicit) - Memory efficient for deep graphs - Good for path finding, cycle detection

DFS vs BFS: DFS = Deep before wide (children before siblings) | BFS = Wide before deep (siblings before children)

DFS Tree Traversal Orders


Big O Complexity

DFS Time and Space Complexity


When to Use

When to Use DFS


Recursive vs Iterative

Recursive vs Iterative DFS


Implementation

Recursive DFS - Graph

public void dfsRecursive(Map<Integer, List<Integer>> graph, int start) {
    Set<Integer> visited = new HashSet<>();
    dfs(graph, start, visited);
}

private void dfs(Map<Integer, List<Integer>> graph, int node, Set<Integer> visited) {
    visited.add(node);
    System.out.print(node + " ");

    for (int neighbor : graph.getOrDefault(node, new ArrayList<>())) {
        if (!visited.contains(neighbor)) {
            dfs(graph, neighbor, visited);
        }
    }
}

Iterative DFS - Graph

public void dfsIterative(Map<Integer, List<Integer>> graph, int start) {
    Set<Integer> visited = new HashSet<>();
    Stack<Integer> stack = new Stack<>();

    stack.push(start);

    while (!stack.isEmpty()) {
        int node = stack.pop();

        if (visited.contains(node)) continue;

        visited.add(node);
        System.out.print(node + " ");

        // Add neighbors in reverse for same order as recursive
        List<Integer> neighbors = graph.getOrDefault(node, new ArrayList<>());
        for (int i = neighbors.size() - 1; i >= 0; i--) {
            if (!visited.contains(neighbors.get(i))) {
                stack.push(neighbors.get(i));
            }
        }
    }
}

Tree Traversals (Recursive)

// Preorder: Root → Left → Right
public void preorder(TreeNode root) {
    if (root == null) return;
    System.out.print(root.val + " ");
    preorder(root.left);
    preorder(root.right);
}

// Inorder: Left → Root → Right
public void inorder(TreeNode root) {
    if (root == null) return;
    inorder(root.left);
    System.out.print(root.val + " ");
    inorder(root.right);
}

// Postorder: Left → Right → Root
public void postorder(TreeNode root) {
    if (root == null) return;
    postorder(root.left);
    postorder(root.right);
    System.out.print(root.val + " ");
}

Tree Traversals (Iterative)

// Iterative Preorder
public List<Integer> preorderIterative(TreeNode root) {
    List<Integer> result = new ArrayList<>();
    if (root == null) return result;

    Stack<TreeNode> stack = new Stack<>();
    stack.push(root);

    while (!stack.isEmpty()) {
        TreeNode node = stack.pop();
        result.add(node.val);

        if (node.right != null) stack.push(node.right);
        if (node.left != null) stack.push(node.left);
    }

    return result;
}

// Iterative Inorder
public List<Integer> inorderIterative(TreeNode root) {
    List<Integer> result = new ArrayList<>();
    Stack<TreeNode> stack = new Stack<>();
    TreeNode current = root;

    while (current != null || !stack.isEmpty()) {
        while (current != null) {
            stack.push(current);
            current = current.left;
        }
        current = stack.pop();
        result.add(current.val);
        current = current.right;
    }

    return result;
}

// Iterative Postorder (using two stacks)
public List<Integer> postorderIterative(TreeNode root) {
    List<Integer> result = new ArrayList<>();
    if (root == null) return result;

    Stack<TreeNode> stack1 = new Stack<>();
    Stack<TreeNode> stack2 = new Stack<>();
    stack1.push(root);

    while (!stack1.isEmpty()) {
        TreeNode node = stack1.pop();
        stack2.push(node);

        if (node.left != null) stack1.push(node.left);
        if (node.right != null) stack1.push(node.right);
    }

    while (!stack2.isEmpty()) {
        result.add(stack2.pop().val);
    }

    return result;
}

Common Problems

Cycle Detection - Undirected Graph

public boolean hasCycleUndirected(Map<Integer, List<Integer>> graph) {
    Set<Integer> visited = new HashSet<>();

    for (int node : graph.keySet()) {
        if (!visited.contains(node)) {
            if (dfsCycleUndirected(graph, node, -1, visited)) {
                return true;
            }
        }
    }

    return false;
}

private boolean dfsCycleUndirected(Map<Integer, List<Integer>> graph,
                                    int node, int parent, Set<Integer> visited) {
    visited.add(node);

    for (int neighbor : graph.getOrDefault(node, new ArrayList<>())) {
        if (!visited.contains(neighbor)) {
            if (dfsCycleUndirected(graph, neighbor, node, visited)) {
                return true;
            }
        } else if (neighbor != parent) {
            return true;  // Back edge found
        }
    }

    return false;
}

Cycle Detection - Directed Graph

public boolean hasCycleDirected(Map<Integer, List<Integer>> graph, int numNodes) {
    int[] state = new int[numNodes];  // 0=unvisited, 1=visiting, 2=visited

    for (int i = 0; i < numNodes; i++) {
        if (state[i] == 0) {
            if (dfsCycleDirected(graph, i, state)) {
                return true;
            }
        }
    }

    return false;
}

private boolean dfsCycleDirected(Map<Integer, List<Integer>> graph,
                                  int node, int[] state) {
    state[node] = 1;  // Visiting

    for (int neighbor : graph.getOrDefault(node, new ArrayList<>())) {
        if (state[neighbor] == 1) return true;  // Back edge
        if (state[neighbor] == 0 && dfsCycleDirected(graph, neighbor, state)) {
            return true;
        }
    }

    state[node] = 2;  // Visited
    return false;
}

Topological Sort

public List<Integer> topologicalSort(Map<Integer, List<Integer>> graph, int numNodes) {
    boolean[] visited = new boolean[numNodes];
    Stack<Integer> stack = new Stack<>();

    for (int i = 0; i < numNodes; i++) {
        if (!visited[i]) {
            dfsTopological(graph, i, visited, stack);
        }
    }

    List<Integer> result = new ArrayList<>();
    while (!stack.isEmpty()) {
        result.add(stack.pop());
    }
    return result;
}

private void dfsTopological(Map<Integer, List<Integer>> graph,
                            int node, boolean[] visited, Stack<Integer> stack) {
    visited[node] = true;

    for (int neighbor : graph.getOrDefault(node, new ArrayList<>())) {
        if (!visited[neighbor]) {
            dfsTopological(graph, neighbor, visited, stack);
        }
    }

    stack.push(node);  // Add after all descendants
}

Number of Islands (Grid DFS)

public int numIslands(char[][] grid) {
    if (grid == null || grid.length == 0) return 0;

    int count = 0;
    int rows = grid.length, cols = grid[0].length;

    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            if (grid[i][j] == '1') {
                dfs(grid, i, j);
                count++;
            }
        }
    }

    return count;
}

private void dfs(char[][] grid, int row, int col) {
    if (row < 0 || row >= grid.length ||
        col < 0 || col >= grid[0].length ||
        grid[row][col] == '0') {
        return;
    }

    grid[row][col] = '0';  // Mark visited

    dfs(grid, row + 1, col);
    dfs(grid, row - 1, col);
    dfs(grid, row, col + 1);
    dfs(grid, row, col - 1);
}

Path Exists in Graph

public boolean validPath(int n, int[][] edges, int source, int destination) {
    Map<Integer, List<Integer>> graph = new HashMap<>();

    for (int[] edge : edges) {
        graph.computeIfAbsent(edge[0], k -> new ArrayList<>()).add(edge[1]);
        graph.computeIfAbsent(edge[1], k -> new ArrayList<>()).add(edge[0]);
    }

    Set<Integer> visited = new HashSet<>();
    return dfs(graph, source, destination, visited);
}

private boolean dfs(Map<Integer, List<Integer>> graph,
                    int current, int target, Set<Integer> visited) {
    if (current == target) return true;

    visited.add(current);

    for (int neighbor : graph.getOrDefault(current, new ArrayList<>())) {
        if (!visited.contains(neighbor)) {
            if (dfs(graph, neighbor, target, visited)) {
                return true;
            }
        }
    }

    return false;
}

All Paths from Source to Target

public List<List<Integer>> allPathsSourceTarget(int[][] graph) {
    List<List<Integer>> result = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    path.add(0);
    dfs(graph, 0, path, result);
    return result;
}

private void dfs(int[][] graph, int node,
                 List<Integer> path, List<List<Integer>> result) {
    if (node == graph.length - 1) {
        result.add(new ArrayList<>(path));
        return;
    }

    for (int neighbor : graph[node]) {
        path.add(neighbor);
        dfs(graph, neighbor, path, result);
        path.remove(path.size() - 1);  // Backtrack
    }
}

Clone Graph

public Node cloneGraph(Node node) {
    if (node == null) return null;

    Map<Node, Node> visited = new HashMap<>();
    return dfs(node, visited);
}

private Node dfs(Node node, Map<Node, Node> visited) {
    if (visited.containsKey(node)) {
        return visited.get(node);
    }

    Node clone = new Node(node.val);
    visited.put(node, clone);

    for (Node neighbor : node.neighbors) {
        clone.neighbors.add(dfs(neighbor, visited));
    }

    return clone;
}

Backtracking Template

// General backtracking pattern using DFS
void backtrack(State state, List<Solution> result) {
    if (isGoal(state)) {
        result.add(state.copy());
        return;
    }

    for (Choice choice : getChoices(state)) {
        if (isValid(choice, state)) {
            makeChoice(choice, state);     // Apply
            backtrack(state, result);
            undoChoice(choice, state);     // Undo (backtrack)
        }
    }
}

N-Queens Example

public List<List<String>> solveNQueens(int n) {
    List<List<String>> result = new ArrayList<>();
    char[][] board = new char[n][n];
    for (char[] row : board) Arrays.fill(row, '.');

    backtrack(board, 0, result);
    return result;
}

private void backtrack(char[][] board, int row, List<List<String>> result) {
    if (row == board.length) {
        result.add(construct(board));
        return;
    }

    for (int col = 0; col < board.length; col++) {
        if (isValid(board, row, col)) {
            board[row][col] = 'Q';
            backtrack(board, row + 1, result);
            board[row][col] = '.';
        }
    }
}

private boolean isValid(char[][] board, int row, int col) {
    for (int i = 0; i < row; i++) {
        if (board[i][col] == 'Q') return false;
    }
    for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
        if (board[i][j] == 'Q') return false;
    }
    for (int i = row - 1, j = col + 1; i >= 0 && j < board.length; i--, j++) {
        if (board[i][j] == 'Q') return false;
    }
    return true;
}

Tips & Tricks

DFS Tips and Tricks