Algorithm Templates: DFS
Depth-First Search (DFS) is one of the most fundamental graph traversal algorithms. It works by starting at a node and exploring as far down each branch as possible before backtracking — making it ideal for problems involving reachability, connected components, paths, and tree structure. This page collects ready-to-use C++ templates for the most common DFS patterns you’ll encounter on LeetCode. See also Graph and Backtracking.
New to DFS? DFS explores as deep as possible before backtracking. Think of it like exploring a maze — go straight until you hit a dead end, then back up and try the next turn.
Contents
Basic DFS
Depth-First Search explores as far as possible before backtracking.
When to use: Checking reachability between nodes, finding connected components, or exploring all paths in a general graph.
# DFS on graph (adjacency list)
def dfs_graph(graph: list[list[int]], node: int, visited: list[bool]) -> None:
visited[node] = True
# process node
for neighbor in graph[node]:
if not visited[neighbor]:
dfs_graph(graph, neighbor, visited)
def dfs_find_target(
graph: list[list[int]], node: int, target: int, visited: list[bool]
) -> bool:
if node == target:
return True
visited[node] = True
for neighbor in graph[node]:
if not visited[neighbor] and dfs_find_target(graph, neighbor, target, visited):
return True
return False
| ID | Title | Link | Solution |
|---|---|---|---|
| 841 | Keys and Rooms | Link | Solution |
DFS on Grid
DFS for 2D grid problems (connected components, paths).
When to use: Flood-fill problems, island counting, or any task where you explore connected cells in a 2D matrix.
# DFS on 2D grid (4-directional)
DIRS = [(0, 1), (0, -1), (1, 0), (-1, 0)]
def dfs_grid(grid: list[list[str]], i: int, j: int) -> None:
m, n = len(grid), len(grid[0])
if i < 0 or i >= m or j < 0 or j >= n or grid[i][j] != "1":
return
grid[i][j] = "0"
for di, dj in DIRS:
dfs_grid(grid, i + di, j + dj)
def num_islands(grid: list[list[str]]) -> int:
m, n = len(grid), len(grid[0])
count = 0
for i in range(m):
for j in range(n):
if grid[i][j] == "1":
count += 1
dfs_grid(grid, i, j)
return count
def dfs_word_search(board: list[list[str]], i: int, j: int, word: str, idx: int) -> bool:
if idx == len(word):
return True
if i < 0 or i >= len(board) or j < 0 or j >= len(board[0]):
return False
if board[i][j] != word[idx]:
return False
temp = board[i][j]
board[i][j] = "#"
for di, dj in DIRS:
if dfs_word_search(board, i + di, j + dj, word, idx + 1):
board[i][j] = temp
return True
board[i][j] = temp
return False
| ID | Title | Link | Solution |
|---|---|---|---|
| 200 | Number of Islands | Link | Solution |
| 79 | Word Search | Link | Solution |
| 695 | Max Area of Island | Link | Solution |
| 133 | Clone Graph | Link | Solution |
| 417 | Pacific Atlantic Water Flow | Link | Solution |
| 323 | Number of Connected Components | Link | Solution |
| 547 | Number of Provinces | Link | Solution |
DFS on Tree
DFS for tree problems (preorder, inorder, postorder).
When to use: Tree traversals, path-sum problems, computing tree height/diameter, or any recursive tree decomposition.
class TreeNode:
def __init__(self, val: int = 0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def preorder(root: TreeNode | None, result: list[int]) -> None:
if not root:
return
result.append(root.val)
preorder(root.left, result)
preorder(root.right, result)
def inorder(root: TreeNode | None, result: list[int]) -> None:
if not root:
return
inorder(root.left, result)
result.append(root.val)
inorder(root.right, result)
def postorder(root: TreeNode | None, result: list[int]) -> None:
if not root:
return
postorder(root.left, result)
postorder(root.right, result)
result.append(root.val)
def has_path_sum(root: TreeNode | None, target_sum: int) -> bool:
if not root:
return False
if not root.left and not root.right:
return root.val == target_sum
return has_path_sum(root.left, target_sum - root.val) or has_path_sum(
root.right, target_sum - root.val
)
def sum_numbers(root: TreeNode | None, cur: int = 0) -> int:
if not root:
return 0
cur = cur * 10 + root.val
if not root.left and not root.right:
return cur
return sum_numbers(root.left, cur) + sum_numbers(root.right, cur)
| ID | Title | Link | Solution |
|---|---|---|---|
| 100 | Same Tree | Link | Solution |
| 101 | Symmetric Tree | Link | Solution |
| 104 | Maximum Depth of Binary Tree | Link | Solution |
| 111 | Minimum Depth of Binary Tree | Link | Solution |
| 112 | Path Sum | Link | Solution |
| 129 | Sum Root to Leaf Numbers | Link | Solution |
| 226 | Invert Binary Tree | Link | Solution |
| 236 | Lowest Common Ancestor | Link | Solution |
| 437 | Path Sum III | Link | Solution |
| 690 | Employee Importance | Link | Solution |
DFS with Memoization
DFS with caching to avoid recomputation.
When to use: Problems with overlapping subproblems on graphs or grids, such as longest increasing path or counting distinct paths.
DIRS4 = [(0, 1), (0, -1), (1, 0), (-1, 0)]
def dfs_with_memo(
matrix: list[list[int]], i: int, j: int, memo: list[list[int]], prev: int
) -> int:
m, n = len(matrix), len(matrix[0])
if i < 0 or i >= m or j < 0 or j >= n or matrix[i][j] <= prev:
return 0
if memo[i][j] != -1:
return memo[i][j]
best = 1
for di, dj in DIRS4:
best = max(best, 1 + dfs_with_memo(matrix, i + di, j + dj, memo, matrix[i][j]))
memo[i][j] = best
return best
| ID | Title | Link | Solution |
|---|---|---|---|
| 329 | Longest Increasing Path in a Matrix | Link | Solution |
Iterative DFS
DFS using stack instead of recursion.
When to use: When the recursion depth might cause a stack overflow, or when you need explicit control over the traversal order.
class TreeNode:
def __init__(self, val: int = 0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def dfs_iterative(graph: list[list[int]], start: int) -> None:
st: list[int] = [start]
visited = [False] * len(graph)
while st:
node = st.pop()
if visited[node]:
continue
visited[node] = True
for nei in reversed(graph[node]):
if not visited[nei]:
st.append(nei)
def preorder_iterative(root: TreeNode | None) -> list[int]:
if not root:
return []
result: list[int] = []
st: list[TreeNode] = [root]
while st:
node = st.pop()
result.append(node.val)
if node.right:
st.append(node.right)
if node.left:
st.append(node.left)
return result
| ID | Title | Link | Solution |
|---|---|---|---|
| 144 | Binary Tree Preorder Traversal | Link | - |
| 94 | Binary Tree Inorder Traversal | Link | - |
Pattern Comparison
| Pattern | When to Use | Time | Space |
|---|---|---|---|
| Basic DFS | Reachability, connected components | O(V+E) | O(V) |
| Grid DFS | Flood fill, island counting | O(M×N) | O(M×N) |
| Tree DFS | All tree traversals, path problems | O(N) | O(H) |
| DFS + Memo | Overlapping subproblems on graphs/grids | O(States) | O(States) |
| Iterative | When recursion stack overflows | O(V+E) | O(V) |
DFS vs BFS
When should you pick DFS over BFS (or vice versa)?
- Use DFS when you need to explore all paths, check connectivity, detect cycles, or solve problems that decompose recursively (e.g., tree shape, backtracking). DFS is also more memory-efficient on narrow/deep structures.
- Use BFS when you need the shortest path in an unweighted graph, want to process nodes level by level, or need the minimum number of steps to reach a target.
- Rule of thumb: If the problem says “shortest” or “minimum steps,” reach for BFS. If it says “all paths,” “connected,” or “exists,” DFS is usually the natural fit.
More templates
- Graph, Backtracking: Graph, Backtracking
- Data structures, Search: Data Structures & Core Algorithms, Search
- Beginner’s Guide: LeetCode Beginner’s Guide
- Master index: Categories & Templates