Skip to content

Commit 7e60bac

Browse files
committed
create word search path retrieval with comprehensive doctests
1 parent 840ca00 commit 7e60bac

File tree

1 file changed

+167
-0
lines changed

1 file changed

+167
-0
lines changed

backtracking/word_search_path.py

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
"""
2+
Author : Nelson Mak
3+
Date : April 5, 2026
4+
5+
Task:
6+
Given an m x n grid of characters board and a string word,
7+
return the first valid path of coordinates found that matches
8+
the word in the grid. If the word does not exist, return None.
9+
10+
The word can be constructed from letters of sequentially adjacent cells,
11+
where adjacent cells are horizontally or vertically neighboring.
12+
The same letter cell may not be used more than once.
13+
14+
Example:
15+
16+
Matrix:
17+
---------
18+
|C|A|T|
19+
|X|Y|S|
20+
|A|B|C|
21+
---------
22+
23+
Word: "CATS"
24+
25+
Result: [(0, 0), (0, 1), (0, 2), (1, 2)]
26+
27+
Implementation notes:
28+
1. Use a backtracking (DFS) approach to explore all possible paths.
29+
2. At each cell, recursively check neighbors, in this question it's (Up, Down, Left, Right).
30+
3. Maintain a 'visited' set for each coordinate
31+
to ensure cells are not reused within the same search branch.
32+
4. If a path matches the word, return the list of coordinates.
33+
5. If a branch fails, 'backtrack' by removing the current cell from
34+
the visited set and the path list to allow for other potential matches.
35+
36+
Similar leetcode question that returns a bool: https://leetcode.com/problems/word-search/
37+
"""
38+
39+
40+
def get_point_key(len_board: int, len_board_column: int, row: int, column: int) -> int:
41+
"""
42+
Returns the hash key of matrix indexes.
43+
44+
>>> get_point_key(10, 20, 1, 0)
45+
200
46+
"""
47+
48+
return len_board * len_board_column * row + column
49+
50+
51+
def get_word_path(
52+
board: list[list[str]],
53+
word: str,
54+
row: int,
55+
column: int,
56+
word_index: int,
57+
visited_points_set: set[int],
58+
current_path: list[tuple[int, int]],
59+
) -> list[tuple[int, int]] | None:
60+
"""
61+
Return the coordinate path if it's possible to find the word in the grid.
62+
"""
63+
64+
if board[row][column] != word[word_index]:
65+
return None
66+
67+
# Track current progress
68+
new_path = current_path + [(row, column)]
69+
70+
# Base case
71+
if word_index == len(word) - 1:
72+
return new_path
73+
74+
traverts_directions = [(0, 1), (0, -1), (-1, 0), (1, 0)]
75+
len_board = len(board)
76+
len_board_column = len(board[0])
77+
for direction in traverts_directions:
78+
next_i = row + direction[0]
79+
next_j = column + direction[1]
80+
81+
if not (0 <= next_i < len_board and 0 <= next_j < len_board_column):
82+
continue
83+
84+
key = get_point_key(len_board, len_board_column, next_i, next_j)
85+
if key in visited_points_set:
86+
continue
87+
88+
visited_points_set.add(key)
89+
result = get_word_path(
90+
board, word, next_i, next_j, word_index + 1, visited_points_set, new_path
91+
)
92+
93+
if result is not None:
94+
return result
95+
96+
# Backtrack: remove key to try other paths
97+
visited_points_set.remove(key)
98+
99+
return None
100+
101+
102+
def word_search_path(board: list[list[str]], word: str) -> list[tuple[int, int]] | None:
103+
"""
104+
>>> # 1. Word is found case
105+
>>> word_search_path([["C","A","T"],["X","Y","S"],["A","B","C"]], "CATS")
106+
[(0, 0), (0, 1), (0, 2), (1, 2)]
107+
108+
>>> # 2. Word is not found case
109+
>>> word_search_path([["A","B"],["C","D"]], "ABCD") is None
110+
True
111+
112+
>>> # 3. Word is a single char
113+
>>> word_search_path([["A"]], "A")
114+
[(0, 0)]
115+
116+
>>> # 4. Invalid board error (empty list)
117+
>>> word_search_path([], "CAT")
118+
Traceback (most recent call last):
119+
...
120+
ValueError: The board should be a non empty matrix of single chars strings.
121+
122+
>>> # 5. Invalid word error
123+
>>> word_search_path([["A"]], 123)
124+
Traceback (most recent call last):
125+
...
126+
ValueError: The word parameter should be a string of length greater than 0.
127+
"""
128+
# Validation
129+
board_error_message = (
130+
"The board should be a non empty matrix of single chars strings."
131+
)
132+
133+
# Validate board input
134+
if not isinstance(board, list) or len(board) == 0:
135+
raise ValueError(board_error_message)
136+
137+
for row in board:
138+
if not isinstance(row, list) or len(row) == 0:
139+
raise ValueError(board_error_message)
140+
for item in row:
141+
if not isinstance(item, str) or len(item) != 1:
142+
raise ValueError(board_error_message)
143+
144+
# Validate word input
145+
if not isinstance(word, str) or len(word) == 0:
146+
raise ValueError(
147+
"The word parameter should be a string of length greater than 0."
148+
)
149+
150+
rows = len(board)
151+
cols = len(board[0])
152+
# Main entry point
153+
for r in range(rows):
154+
for c in range(cols):
155+
# Optimization: only trigger recursion if first char in board matches with first char in word input
156+
if board[r][c] == word[0]:
157+
key = get_point_key(rows, cols, r, c)
158+
path_result = get_word_path(board, word, r, c, 0, {key}, [])
159+
if path_result is not None:
160+
return path_result
161+
162+
return None
163+
164+
165+
if __name__ == "__main__":
166+
import doctest
167+
doctest.testmod()

0 commit comments

Comments
 (0)