Problem
Given a 2D grid of characters and a target word, determine whether the word can be formed by following a path of adjacent cells โ horizontally or vertically โ where no cell may be used more than once in a single path.
A B C E
S F C S
A D E E
- Input: the board above,
word = "ABCCED" - Output:
true - Explanation: the path A(0,0) โ B(0,1) โ C(0,2) โ C(1,2) โ E(2,2) โ D(2,1) spells the word
Counter-example: word = "ABCB" returns false โ after reaching B at (0,1), there is no way to get back to B without reusing the cell.
Intuition
This is a search problem with a "no revisiting" constraint, which makes it a natural fit for backtracking. The key insight is that the board state changes as we walk a path โ we temporarily mark cells visited โ and then we restore the board when we retreat, so other paths can use those cells. There is no smarter algorithm than trying every valid path; the gains come from cutting off failed paths as early as possible.
Solution โ Backtracking DFS
For every cell that matches the first character of the word, launch a depth-first search. At each step, check bounds, the current character, and whether the cell is already on the active path. Mark the cell to prevent reuse, recurse into all four neighbors, then restore the cell before returning so other paths can use it.
- Scan every cell; when
board[r][c]matchesword[0], callbacktrack(r, c, 0). - In
backtrack: ifindex == len(word), all characters matched โ returnTrue. - Return
Falseimmediately if out of bounds or the cell doesn't matchword[index](this also catches the visited marker'#'since'#'is never a letter). - Save the current character, then overwrite the cell with
'#'to mark it visited. - Recurse into the four neighbors with
index + 1; combine results withor(short-circuits on firstTrue). - Restore the cell to its saved character before returning โ this is the backtrack step.
1def exist(board: list[list[str]], word: str) -> bool:
2 rows, cols = len(board), len(board[0])
3
4 def backtrack(r: int, c: int, index: int) -> bool:
5 if index == len(word):
6 return True # every character in word has been matched
7 if r < 0 or r >= rows or c < 0 or c >= cols:
8 return False
9 if board[r][c] != word[index]:
10 return False # wrong character, or '#' sentinel from a visited cell
11
12 original_char = board[r][c]
13 board[r][c] = '#' # mark visited to block reuse on this path
14
15 found = (backtrack(r + 1, c, index + 1) or
16 backtrack(r - 1, c, index + 1) or
17 backtrack(r, c + 1, index + 1) or
18 backtrack(r, c - 1, index + 1))
19
20 board[r][c] = original_char # restore cell so other paths can use it
21 return found
22
23 for r in range(rows):
24 for c in range(cols):
25 if backtrack(r, c, 0):
26 return True
27
28 return FalseTime: O(m ร n ร 4^L) โ from each of the mรn starting cells the DFS explores at most 4^L paths (L = word length); in practice much faster because mismatched characters prune branches immediately.
Space: O(L) โ the recursion call stack goes at most L frames deep, one per character in the word.
Complexity Summary
| Approach | Time | Space | When to use |
|---|---|---|---|
| Backtracking DFS | O(m ร n ร 4^L) | O(L) | The only general approach; prune aggressively with the visited marker |
Common Mistakes
- Not restoring the cell after backtracking โ if you mark a cell
'#'but never reset it, cells used in one failed path stay marked and are unavailable to paths starting from other cells, causing falsefalseresults. - Checking
board[r][c]before validating bounds โ accessing an out-of-bounds index throws an error; always checkrandcagainst the grid dimensions before touchingboard[r][c]. - Marking the cell visited after the recursive calls instead of before โ a neighbor can then recurse back into the current cell and reuse it, producing incorrect paths where the same cell appears twice.
- Returning
Truewhenindex == len(word) - 1and checking the character at the same time โ the cleaner base case isif index == len(word): return Trueat the top of the function, checked before any bounds or character validation; this avoids the off-by-one where you also need to confirm the last character matches. - Using
orwith four separateifstatements that all return early โ if the first direction returnsTrue, the early return is correct, but if you structure it as four independentif backtrack(...): return Trueblocks without theor, you may forget that a failed branch should not immediately returnFalseโ you need to try all four before giving up.
Related Problems
Word Search IIโ extends this to finding all words from a list simultaneously using a Trie to avoid redundant pathsNumber of Islandsโ the same DFS flood-fill skeleton on a grid, but without the character-matching or backtracking requirementPath Sum IIโ backtracking on a tree to collect all root-to-leaf paths that hit a target, same mark-recurse-unmark patternN-Queensโ classic backtracking with a constraint check before placing each piece, same shape as checkingword[index]before recursingCombination Sum IIโ backtracking over a flat list with a "no reuse" constraint, analogous to the visited-cell restriction here