Skip to content

Commit 9a95063

Browse files
committed
Word Search II
1 parent 02fe5a1 commit 9a95063

File tree

4 files changed

+170
-0
lines changed

4 files changed

+170
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
| 0206 | Easy | Reverse Linked List | Linked List, Recursion | [solution](./docs/0206-Reverse-Linked-List.md) |
4848
| 0208 | Medium | Implement Trie (Prefix Tree) | Hash Table, String, Design, Trie | [solution](./docs/0208-Implement-Trie-(Prefix-Tree).md) |
4949
| 0211 | Medium | Design Add and Search Words Data Structure | String, Depth-First Search, Design, Trie | [solution](./docs/0211-Desing-Add-and-Search-Words-Data-Structure.md) |
50+
| 0212 | Hard | Word Search II | Array, String, Backtracking, Trie, Matrix | [solution](./docs/0212-Word-Search-II.md) |
5051
| 0217 | Easy | Contains Duplicate | Array | [solution](./docs/0217-Contains-Duplicate.md) |
5152
| 0219 | Easy | Contains Duplicate II | Array | [solution](./docs/0219-Contains-Duplicate-II.md) |
5253
| 0226 | Easy | Invert Binary Tree | Tree, Depth-First Search, Breadth-First Search, Binary Tree | [solution](./docs/0226-Invert-Binary-Tree.md) |

docs/0212-Word-Search-II.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# 🧠 [Word Search II](https://leetcode.com/problems/word-search-ii/description/)
2+
3+
## 💡 Intuition
4+
5+
We need to search for all words from a given list that can be formed by traversing adjacent letters on a 2D grid. The
6+
brute-force approach would involve scanning the board for each word, which is inefficient. Instead, we build a **Trie
7+
** (prefix tree) from the word list to enable prefix pruning during DFS. This avoids unnecessary traversal and
8+
accelerates the search significantly.
9+
10+
## 🔍 Approach
11+
12+
1. **Trie Construction**:
13+
We create a Trie data structure where each node represents a character. Words are inserted into this Trie, marking
14+
the end of each valid word.
15+
16+
2. **DFS Traversal**:
17+
From every cell on the board, we initiate a DFS. During the traversal:
18+
- We check if the current path matches a prefix in the Trie.
19+
- If a word is found (a Trie node is marked `end`), we add it to the result set.
20+
- To prevent revisiting the same cell, we maintain a set of visited cells.
21+
- We perform DFS in four directions: up, down, left, and right.
22+
23+
3. **Optimization**:
24+
- Use a `Set` to store results and prevent duplicates.
25+
- Stop early in DFS if the prefix is invalid (not in Trie).
26+
27+
## ⏱️ Complexity
28+
29+
- **Time Complexity**:
30+
`O(M × N × 4^L)` in the worst case, where:
31+
- `M × N` is the size of the board,
32+
- `L` is the length of the longest word,
33+
- `4^L` is the number of DFS paths (each cell can branch to 4 neighbors).
34+
35+
However, due to Trie pruning, the practical complexity is much lower.
36+
37+
- **Space Complexity**:
38+
- `O(K × L)` for Trie where `K` is number of words and `L` is average word length.
39+
- `O(L)` for the DFS call stack.
40+
- `O(W)` for the result set, where `W` is the number of matched words.
41+
42+
## 🧪 Code
43+
44+
- [Java](../src/main/java/io/dksifoua/leetcode/wordsearch2/Solution.java)
45+
46+
## ✅ Summary
47+
48+
Using a Trie to store words allows efficient prefix-based pruning of DFS paths. This avoids redundant searches and
49+
enables solving the problem within constraints, even for large boards and word lists. Combining Trie with DFS provides a
50+
powerful pattern for grid-based word search problems.
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package io.dksifoua.leetcode.wordsearch2;
2+
3+
import java.util.HashSet;
4+
import java.util.List;
5+
import java.util.Set;
6+
7+
public class Solution {
8+
9+
private Solution[] children;
10+
private boolean end;
11+
12+
public Solution() {
13+
this.children = new Solution[26];
14+
this.end = false;
15+
}
16+
17+
private void reset() {
18+
this.children = new Solution[26];
19+
this.end = false;
20+
}
21+
22+
private void insert(String word) {
23+
Solution node = this;
24+
for (char c : word.toCharArray()) {
25+
int index = c - 'a';
26+
if (node.children[index] == null) {
27+
node.children[index] = new Solution();
28+
}
29+
node = node.children[index];
30+
}
31+
node.end = true;
32+
}
33+
34+
public List<String> findWords(char[][] board, String[] words) {
35+
this.reset();
36+
for (String word : words) {
37+
this.insert(word);
38+
}
39+
40+
Set<String> result = new HashSet<>();
41+
Set<Tuple<Integer, Integer>> visitedCells = new HashSet<>();
42+
for (int row = 0; row < board.length; row++) {
43+
for (int col = 0; col < board[row].length; col++) {
44+
this.dfs(board, row, col, visitedCells, result, "");
45+
}
46+
}
47+
48+
return result.stream().toList();
49+
}
50+
51+
private void dfs(char[][] board, int row, int col, Set<Tuple<Integer, Integer>> visitedCells, Set<String> result, String word) {
52+
if (row < 0 || col < 0 || row >= board.length || col >= board[row].length) return;
53+
54+
int index = board[row][col] - 'a';
55+
if (this.children[index] == null) return;
56+
57+
Tuple<Integer, Integer> cell = new Tuple<>(row, col);
58+
if (visitedCells.contains(cell)) return;
59+
60+
word += board[row][col];
61+
visitedCells.add(cell);
62+
63+
if (this.children[index].end) result.add(word);
64+
65+
this.children[index].dfs(board, row - 1, col, visitedCells, result, word);
66+
this.children[index].dfs(board, row + 1, col, visitedCells, result, word);
67+
this.children[index].dfs(board, row, col - 1, visitedCells, result, word);
68+
this.children[index].dfs(board, row, col + 1, visitedCells, result, word);
69+
70+
visitedCells.remove(cell);
71+
}
72+
73+
private static record Tuple<T, U>(T a, U b) {
74+
}
75+
76+
;
77+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package io.dksifoua.leetcode.wordsearch2;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import java.util.List;
6+
7+
import static org.junit.jupiter.api.Assertions.assertEquals;
8+
import static org.junit.jupiter.api.Assertions.assertTrue;
9+
10+
class SolutionTest {
11+
12+
@Test
13+
void testExample1() {
14+
Solution solution = new Solution();
15+
char[][] board = {
16+
{ 'o', 'a', 'a', 'n' },
17+
{ 'e', 't', 'a', 'e' },
18+
{ 'i', 'h', 'k', 'r' },
19+
{ 'i', 'f', 'l', 'v' }
20+
};
21+
String[] words = { "oath", "pea", "eat", "rain" };
22+
List<String> result = solution.findWords(board, words);
23+
24+
assertTrue(result.contains("oath"));
25+
assertTrue(result.contains("eat"));
26+
assertEquals(2, result.size());
27+
}
28+
29+
@Test
30+
void testExample2() {
31+
Solution solution = new Solution();
32+
char[][] board = {
33+
{ 'a', 'b' },
34+
{ 'c', 'd' }
35+
};
36+
String[] words = { "abcb" };
37+
List<String> result = solution.findWords(board, words);
38+
39+
assertTrue(result.isEmpty());
40+
}
41+
42+
}

0 commit comments

Comments
 (0)