79. Word Search - Explanation
Description
Given a 2-D grid of characters board and a string word, return true if the word is present in the grid, otherwise return false.
For the word to be present it must be possible to form it with a path in the board with horizontally or vertically neighboring cells. The same cell may not be used more than once in a word.
Example 1:
Input:
board = [
["A","B","C","D"],
["S","A","A","T"],
["A","C","A","E"]
],
word = "CAT"
Output: trueExample 2:
Input:
board = [
["A","B","C","D"],
["S","A","A","T"],
["A","C","A","E"]
],
word = "BAT"
Output: falseConstraints:
1 <= board.length, board[i].length <= 51 <= word.length <= 10boardandwordconsists of only lowercase and uppercase English letters.
Topics
Recommended Time & Space Complexity
You should aim for a solution with O(m * (4^n)) time and O(n) space, where m is the number of cells in the given board and n is the size of the given word.
Hint 1
As we can start at any cell on the board, we can explore all paths from that cell. Can you think of an algorithm to do so? Also, you should consider a way to avoid visiting a cell that has already been visited in the current path.
Hint 2
We can use a hash set to avoid revisiting a cell in the current path by inserting the (row, col) of the visiting cell into the hash set and exploring all paths (four directions, as we can move to four neighboring cells) from that cell. Can you think of the base condition for this recursive path? Maybe you should consider the board boundaries, and also, we can extend a path if the character at the cell matches the character in the word.
Hint 3
We can use backtracking, starting from each cell on the board with coordinates (row, col) and index i for the given word. We return false if (row, col) is out of bounds or if board[row][col] != word[i]. When i reaches the end of the word, we return true, indicating a valid path. At each step, we add (row, col) to a hash set to avoid revisiting cells. After exploring the four possible directions, we backtrack and remove (row, col) from the hash set.
Prerequisites
Before attempting this problem, you should be comfortable with:
- Backtracking - Exploring all possible paths by making choices, recursing, and undoing choices to try alternatives
- Depth-First Search (DFS) - Traversing a graph or grid by exploring as far as possible along each branch before backtracking
- 2D Grid Traversal - Moving through a matrix in four directions while tracking visited cells to avoid revisiting
- Recursion - Understanding recursive function calls and base cases for termination
1. Backtracking (Hash Set)
Intuition
We need to check if the word can be formed by walking up/down/left/right on the grid, using each cell at most once in the same path.
So for every cell, we try to start the word there:
- If the current cell matches the current character, we move to its 4 neighbors for the next character.
- While exploring, we mark the cell as visited (in a hash set) so we don't reuse it in the same path.
- If a path fails, we undo (backtrack) the visit and try other directions.
If we ever match all characters, we return true (found the word).
Algorithm
- For each cell in the grid, attempt to start matching
wordfrom that cell. - Use
DFSwith(row, col, i)whereiis the index inwordwe need to match. - In
DFS:- If
i == len(word), all characters matched → returntrue. - If out of bounds, mismatch, or already visited → return
false. - Mark
(row, col)as visited. - Recurse to 4 neighbors with
i + 1. - Unmark
(row, col)(backtrack).
- If
- If any start cell returns
true, answer istrue; otherwisefalse.
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
ROWS, COLS = len(board), len(board[0])
path = set()
def dfs(r, c, i):
if i == len(word):
return True
if (min(r, c) < 0 or
r >= ROWS or c >= COLS or
word[i] != board[r][c] or
(r, c) in path):
return False
path.add((r, c))
res = (dfs(r + 1, c, i + 1) or
dfs(r - 1, c, i + 1) or
dfs(r, c + 1, i + 1) or
dfs(r, c - 1, i + 1))
path.remove((r, c))
return res
for r in range(ROWS):
for c in range(COLS):
if dfs(r, c, 0):
return True
return FalseTime & Space Complexity
- Time complexity:
- Space complexity:
Where is the number of cells in the and is the length of the .
2. Backtracking (Visited Array)
Intuition
We try to form the word by walking through adjacent cells (up, down, left, right) in the grid.
Each cell can be used only once in the current path, so we keep a visited matrix to mark cells that are already part of the path.
From every cell, we attempt to match the word starting at index 0.
If at any point the character doesn't match, goes out of bounds, or the cell is already visited, we stop that path.
If all characters are matched successfully, the word exists in the grid and we return true.
Algorithm
- Create a
visitedmatrix of the same size as the board. - For every cell
(r, c)in the grid, start aDFSto match the word from index0. - In
DFS(r, c, i):- If
i == len(word), all characters are matched → returntrue. - If out of bounds, character mismatch, or already visited → return
false. - Mark the current cell as visited.
- Recurse to the 4 neighboring cells with
i + 1. - Unmark the cell (backtrack).
- If
- If any
DFScall returnstrue, returntrue. - If all starts fail, return
false.
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
ROWS, COLS = len(board), len(board[0])
visited = [[False for _ in range(COLS)] for _ in range(ROWS)]
def dfs(r, c, i):
if i == len(word):
return True
if (r < 0 or c < 0 or r >= ROWS or c >= COLS or
word[i] != board[r][c] or visited[r][c]):
return False
visited[r][c] = True
res = (dfs(r + 1, c, i + 1) or
dfs(r - 1, c, i + 1) or
dfs(r, c + 1, i + 1) or
dfs(r, c - 1, i + 1))
visited[r][c] = False
return res
for r in range(ROWS):
for c in range(COLS):
if dfs(r, c, 0):
return True
return FalseTime & Space Complexity
- Time complexity:
- Space complexity:
Where is the number of cells in the and is the length of the .
3. Backtracking (Optimal)
Intuition
We want to check if the word can be formed by moving up/down/left/right in the grid, using each cell at most once in a single path.
Instead of keeping a separate visited matrix (extra space), we temporarily mark the current cell as used by replacing its character with a special value (like '#').
This means:
- If we ever see
'#', we know this cell is already in our current path → we can't reuse it. - After exploring from that cell, we restore the original character (this is the "backtrack" step), so other paths can use it.
So the idea is:
- Try every cell as a starting point.
- Do
DFSto match the word character by character. - Mark → explore neighbors → unmark.
Algorithm
- Let
ROWS,COLSbe grid size. - Define
dfs(r, c, i)meaning: "Can we matchword[i...]starting from cell(r, c)?" - Base case: if
i == len(word), we matched all characters → returntrue. - Fail cases: if out of bounds, current cell doesn't match
word[i], or cell is already used ('#') → returnfalse. - Mark the cell as used (set it to
'#'). - Try
DFSin 4 directions withi + 1. - Restore the cell back to its original character (backtrack).
- Run
dfs(r, c, 0)from every cell(r, c). If any returnstrue, answer istrue; otherwisefalse.
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
ROWS, COLS = len(board), len(board[0])
def dfs(r, c, i):
if i == len(word):
return True
if (r < 0 or c < 0 or r >= ROWS or c >= COLS or
word[i] != board[r][c] or board[r][c] == '#'):
return False
board[r][c] = '#'
res = (dfs(r + 1, c, i + 1) or
dfs(r - 1, c, i + 1) or
dfs(r, c + 1, i + 1) or
dfs(r, c - 1, i + 1))
board[r][c] = word[i]
return res
for r in range(ROWS):
for c in range(COLS):
if dfs(r, c, 0):
return True
return FalseTime & Space Complexity
- Time complexity:
- Space complexity:
Where is the number of cells in the and is the length of the .
Common Pitfalls
Not Restoring the Cell After Backtracking
When marking a cell as visited by changing its value (e.g., to '#'), forgetting to restore the original character after exploring all neighbors will permanently modify the board. This causes other paths starting from different cells to incorrectly treat valid cells as already visited.
Checking Visited Before Matching Character
Placing the visited check before the character match check can cause subtle bugs. If a cell is marked visited with a special character like '#', comparing board[r][c] != word[i] will correctly fail. However, if using a separate visited structure, the order of checks matters for correctness and clarity.
Forgetting to Match the First Character Before DFS
Starting DFS from every cell without first checking if board[r][c] == word[0] wastes time exploring paths that cannot possibly match the word. While not incorrect, this optimization significantly improves performance on large boards.
Sign in to join the discussion