Word Search II
HardThe problem
Given a grid of letters and a list of words, return every word from the list that can be spelled by walking through the grid. Each step moves up, down, left or right to a touching cell, and one word cannot use the same cell twice. The result can be in any order.
- Example 1Input: board = [["o","a","a","n"],["e","t","a","e"],["i","h","k","r"],["i","f","l","v"]], words = ["oath","pea","eat","rain"]Output: ["oath", "eat"]
"oath" and "eat" can be traced in the grid. "pea" and "rain" cannot.
- Example 2Input: board = [["a","b"],["c","d"]], words = ["abdc","abcd"]Output: ["abdc"]
a → b → d → c works, but b and c are diagonal, so "abcd" does not.
- 1 ≤ rows, columns ≤ 12
- 1 ≤ words.length ≤ 30,000
- 1 ≤ word length ≤ 10
- Only lowercase letters; all words are different
- If no word is found return an empty list
Write it in Go. Try for about 20 minutes on paper first, then open one hint at a time.
Try it here
Write Go. Common packages like fmt and sort are imported for you. Keep the function name and inputs the same.
Hints, one at a time
Nudge
Searching the grid once per word is too slow. Can one search look for all the words at the same time?
The idea
Build a trie of all words, DFS from each cell following trie edges (pruning when no child), mark visited cells, collect words at end nodes and remove found words.
Target: O(cells · 4 · 3^(L−1)) worst-case, pruned heavily by the trie
Go function shape
func findWords(board [][]byte, words []string) []stringReference solution
Tested with go test. Try it yourself first, then compare.
// FindWords searches the board for many words at once. Insert all words into a trie, then DFS from each cell
// following trie edges: a branch with no matching child is pruned immediately. Found words are removed from
// the trie so they are reported once.
type wsNode struct {
next map[byte]*wsNode
word string // non-empty when a word ends here
}
func FindWords(board [][]byte, words []string) []string {
root := &wsNode{next: map[byte]*wsNode{}}
for _, w := range words {
n := root
for i := 0; i < len(w); i++ {
if n.next[w[i]] == nil {
n.next[w[i]] = &wsNode{next: map[byte]*wsNode{}}
}
n = n.next[w[i]]
}
n.word = w
}
found := []string{}
var dfs func(r, c int, n *wsNode)
dfs = func(r, c int, n *wsNode) {
if r < 0 || c < 0 || r >= len(board) || c >= len(board[0]) {
return
}
ch := board[r][c]
child := n.next[ch]
if ch == '#' || child == nil {
return // already used on this path, or no word continues this way
}
if child.word != "" {
found = append(found, child.word)
child.word = "" // report each word once
}
board[r][c] = '#' // mark as used
dfs(r+1, c, child)
dfs(r-1, c, child)
dfs(r, c+1, child)
dfs(r, c-1, child)
board[r][c] = ch // restore
}
for r := range board {
for c := range board[r] {
dfs(r, c, root)
}
}
return found
}