Word Search
Given an M×N board of characters and a word, determine if the word exists by sequentially matching adjacent cells (horizontal or vertical). The classic solution uses DFS backtracking from every matching start cell, with a visited grid to prevent reusing the same cell. This lesson covers LeetCode 79 and implementations in C, Java, and Python with trace tables.
Overview
Word Search is grid DFS with a string index instead of a path string. Each step checks whether the current cell matches the next character; if the full word matches, return true. Failed branches backtrack by unmarking visited cells.
Board (LeetCode 79 classic) word = "ABCCED" A B C E (0,0)A → (0,1)B → (0,2)C S F C S match path → → (1,2)C → (2,2)E → (2,3)D ✓ A D E E Adjacent moves only (no diagonals); each cell used once per path
Problem Statement
Input: 2D character board board[m][n] and string word.
Output: true if word can be constructed from sequentially adjacent cells; otherwise false.
Rules:
- Letters must match in order along the word.
- Only horizontal or vertical neighbors count (4-directional).
- The same cell cannot appear twice in one search path.
Examples (classic board):
"ABCCED"→true"SEE"→true(starts at (1,3) S)"ABCB"→false(would need to reuse B at (0,1))
Backtracking Algorithm
- For each cell
(row, col)whereboard[row][col] == word[0], run DFS. - DFS(row, col, index):
- If
index == len(word), returntrue. - If out of bounds, visited, or char mismatch → return
false. - Mark
visited[row][col] = true. - Try four neighbors with
index + 1; if any returns true, propagate. - Unmark visited and return
false.
- If
index into word, not a direction string.Character Pruning
Before recursing, reject immediately when:
board[row][col] != word[index]— character mismatch- Cell already
visitedon current path roworcolout of bounds
In-place trick: Temporarily replace board[row][col] with '#' instead of a separate visited matrix — restore on backtrack (saves O(m×n) space).
Code (C, Java, Python)
Python
def exist(board, word):
"""Word Search — DFS backtracking with visited grid."""
if not board or not board[0]:
return False
rows, cols = len(board), len(board[0])
visited = [[False] * cols for _ in range(rows)]
dr = [1, -1, 0, 0]
dc = [0, 0, 1, -1]
def dfs(row, col, index):
if index == len(word):
return True
if (row < 0 or row >= rows or col < 0 or col >= cols
or visited[row][col] or board[row][col] != word[index]):
return False
visited[row][col] = True
for k in range(4):
if dfs(row + dr[k], col + dc[k], index + 1):
return True
visited[row][col] = False
return False
for r in range(rows):
for c in range(cols):
if board[r][c] == word[0] and dfs(r, c, 0):
return True
return False
if __name__ == "__main__":
grid = [
list("ABCE"),
list("SFCS"),
list("ADEE"),
]
print(exist(grid, "ABCCED")) # True
print(exist(grid, "ABCB")) # False
Java
public class WordSearch {
private static final int[] DR = {1, -1, 0, 0};
private static final int[] DC = {0, 0, 1, -1};
private static boolean dfs(char[][] board, boolean[][] visited,
String word, int row, int col, int index) {
if (index == word.length()) return true;
if (row < 0 || row >= board.length || col < 0 || col >= board[0].length)
return false;
if (visited[row][col] || board[row][col] != word.charAt(index))
return false;
visited[row][col] = true;
for (int k = 0; k < 4; k++) {
if (dfs(board, visited, word, row + DR[k], col + DC[k], index + 1))
return true;
}
visited[row][col] = false;
return false;
}
public static boolean exist(char[][] board, String word) {
int rows = board.length, cols = board[0].length;
boolean[][] visited = new boolean[rows][cols];
for (int r = 0; r < rows; r++)
for (int c = 0; c < cols; c++)
if (board[r][c] == word.charAt(0) && dfs(board, visited, word, r, c, 0))
return true;
return false;
}
}
C
#include <stdbool.h>
#include <string.h>
int dr[] = {1, -1, 0, 0};
int dc[] = {0, 0, 1, -1};
bool dfs(char board[][10], bool visited[][10], const char *word,
int rows, int cols, int row, int col, int index) {
int k, nr, nc;
if (word[index] == '\0') return true;
if (row < 0 || row >= rows || col < 0 || col >= cols) return false;
if (visited[row][col] || board[row][col] != word[index]) return false;
visited[row][col] = true;
for (k = 0; k < 4; k++) {
nr = row + dr[k];
nc = col + dc[k];
if (dfs(board, visited, word, rows, cols, nr, nc, index + 1))
return true;
}
visited[row][col] = false;
return false;
}
Output & Trace
Sample results (LeetCode board)
| Word | Result | Reason |
|---|---|---|
| ABCCED | true | Path (0,0)→(0,1)→(0,2)→(1,2)→(2,2)→(2,3) |
| SEE | true | Path (1,3)→(2,3)→(2,2) |
| ABCB | false | Need B at (0,1) twice — cell reuse forbidden |
Partial trace — matching "ABCCED"
| Step | Cell | Char | index | Action |
|---|---|---|---|---|
| 1 | (0,0) | A | 0 | Start match, mark visited |
| 2 | (0,1) | B | 1 | Move right |
| 3 | (0,2) | C | 2 | Move right |
| 4 | (1,2) | C | 3 | Move down |
| 5 | (2,2) | E | 4 | Move down |
| 6 | (2,3) | D | 5 | Match complete → return true |
LeetCode 79 & 212
| Problem | Task | Approach |
|---|---|---|
| 79 — Word Search | One word exists? | DFS backtracking from each start cell |
| 212 — Word Search II | Find all words from a list | Trie + DFS; prune when trie prefix dead |
Optimizations
- In-place visited: Mark cells with
'#'— O(1) extra space beyond recursion. - Early return: Stop at first successful DFS — no need to search all starts after match.
- Trie (LC 212): Share prefix exploration across many dictionary words.
- Frequency prune: If board lacks enough of a rare letter, return false before DFS.
Approach Comparison
| Approach | Time | Space | Notes |
|---|---|---|---|
| DFS + visited[] | O(m×n×4^L) | O(L) recursion + O(m×n) visited | Standard LC 79 |
| DFS in-place mark | Same | O(L) recursion | Space-optimized |
| Trie + DFS (LC 212) | Better for many words | O(total chars in dict) | Multi-word search |
L = word length, m×n = board size.
Real-Life Applications
| Domain | Scenario | Mapping |
|---|---|---|
| Word games | Boggle, crossword helpers | Grid letter + dictionary lookup |
| OCR / puzzles | Find hidden words in letter grids | Same DFS template |
| Bioinformatics | Sequence pattern in grids/matrices | Constrained path matching |
| Search autocomplete | Prefix tree on 2D data | Trie extension of word search |
| Interviews | Grid DFS foundation | Leads to island problems, maze, etc. |
Interview Tips
- Clarify: 4-directional only, no cell reuse on one path.
- Loop all cells for first-char match, then DFS with
index. - Always unmark visited (or restore char) on backtrack.
- Mention in-place
'#'trick if asked to optimize space. - Complexity: O(m×n×4^L) worst case; L = word length.
- Connect to rat in a maze and pruning techniques.
Key Takeaway
Word Search = start DFS from each cell matching word[0]; at each step match word[index], mark visited, try four neighbors, unmark on failure. Character mismatch and visited cells are your pruning rules.
Track complete: Backtracking introduction · Algorithms Hub · Rat in a maze