Word Search Solution
Given an m × n grid of characters and a string word, return true if the word exists in the grid via adjacent (horizontally/vertically) cells, each cell used at most once.
Problem Statement
Given an m × n grid of characters board and a string word, return true if word exists in the grid. The word can be constructed from letters of sequentially adjacent cells — horizontally or vertically. The same cell may not be used more than once per path.
Brute DFS with Visited Set — O(m·n·4ᴸ)
From every cell, launch a DFS. Maintain a separate HashSet of visited coordinates. If the current cell matches the current character of the word, recurse into all 4 neighbors; otherwise backtrack. Clear the set before each new starting cell.
Since we track visited cells in a set, no cell is revisited in a single path. Trying all starting positions and exploring all valid 4-directional paths guarantees correctness.
HashSet of coordinate pairs allocates objects on every recursive call. The optimal approach marks visited cells in-place by overwriting the cell with a sentinel character (e.g., '#'), then restoring it on backtrack. This eliminates all allocations for the visited state and is O(1) per cell check.
| Metric | Value |
|---|---|
| Time | O(m · n · 4ᴸ) where L = word length |
| Space | O(L) call stack + O(L) visited set per path |
In-place Backtracking — O(m·n·4ᴸ)
Replace the visited set by marking the current cell with a sentinel character '#' before recursing and restoring it afterward. This achieves the same "no revisit" invariant with zero extra memory per call.
- Search all starting positions: try every cell
(r, c)as the start when it matchesword[0]. - DFS(r, c, k): check bounds, then check
board[r][c] == word[k]. - Mark visited: set
board[r][c] = '#'— sentinel that matches nothing. - Recurse: explore all 4 neighbors for character
word[k+1]. - Restore: set
board[r][c]back to original character (backtrack). - Return true as soon as any path succeeds (short-circuit).
- Any "path exists in 2D grid" problem where cells cannot be reused in a single path.
- The 4-direction DFS + in-place marking is a direct template.
- It also appears in LC 212 (Word Search II — add a Trie), LC 200 (Number of Islands — flood fill), and LC 130 (Surrounded Regions).
- The signal: "find a path on a grid, no revisit."
- Before starting DFS, count character frequencies in the board and compare to the word.
- If any character in the word appears more times in the word than in the board, immediately return false.
- This can eliminate the entire DFS for impossible words in O(m·n + L) time.
Visual Walkthrough
Trace searching for "ABCCED" in the example grid. The successful path is shown step-by-step.
Code — Java & Python
Complexity Analysis
| Approach | Time | Space | Trade-off |
|---|---|---|---|
| Brute DFS + HashSet | O(m·n·4ᴸ) | O(L) set + O(L) stack | Extra set allocation per call; harder to optimize |
| DFS + boolean[][ ] visited | O(m·n·4ᴸ) | O(m·n + L) | Separate grid copy — extra O(m·n) space |
| In-place sentinel ← optimal | O(m·n·4ᴸ) | O(L) call stack only | Mutates input temporarily; zero extra space for visited |
- At each of the L characters in the word, we explore at most 4 directions.
- Practically, one direction is always the cell we came from (which is marked '#'), so the branching factor is ≤ 3.
- But in the worst case (no path is pruned until character L), it is still O(4ᴸ) per starting cell, giving O(m·n·4ᴸ) total.
Edge Cases & Pitfalls
| Case | Input | Why It Matters |
|---|---|---|
| Word longer than all cells | 1×1 grid, 5-char word | Only 1 cell exists; function returns false after matching at most 1 char. |
| Single cell equals word | [["A"]], word="A" | k=0 matches, recursion with k=1 returns true immediately. |
| Word includes same char twice | word="AA" on grid [["A","A"]] | Sentinel '#' prevents revisiting the same cell as A for the second A. |
| Word not present | any grid, word not in grid | All DFS branches exhaust without returning true → return false. |
| Negative or boundary start | word starts at corner | Neighbors go out of bounds → bounds check returns false, no crash. |
| Forgetting to restore cell | any | Leaving '#' in the board means later starting positions see a corrupted grid — false negatives. |
board[r][c] after a failed DFS branch. If you mark a cell '#' and only restore it on success, any subsequent starting position that passes through that cell will erroneously treat it as already visited. The restore must happen unconditionally after the recursive call — whether the call returned true or false.