This is an extension of the classic Word Search problem, but instead of checking one word at a time, we need to find all words from a given list that exist on the board. A naive approach would be to run Word Search I for each word individually, but with up to 30,000 words, that repeats the full board search 30,000 times.
The real question is whether we can share work across multiple word searches. If two words share a common prefix (like "oath" and "oat"), tracing the same path twice to check both words is wasted effort. A Trie (prefix tree) solves this. By inserting all words into a Trie first, we can explore the board once and check against all words at the same time as we traverse.
1 <= m, n <= 12 -> The board has at most 144 cells, small enough for backtracking with pruning.1 <= words.length <= 3 * 10^4 -> Up to 30,000 words. Searching for each word individually multiplies the board traversal by 30,000, which is the main cost to eliminate.1 <= words[i].length <= 10 -> Words are at most 10 characters, which bounds the backtracking depth at 10.words are unique -> The word list has no duplicates, but a single word can still be spelled by more than one board path, so the result must avoid adding it twice.Reuse the solution from Word Search I. For each word in the list, iterate over every cell on the board and run a DFS/backtracking search to see if that word exists. If it does, add it to the result.
This is correct but does a lot of redundant work. If the words "oath" and "oat" are both in the list, the path O -> A -> T gets traced twice, once for each word. With 30,000 words, the full board search runs 30,000 times.
Input:
We run a separate DFS for each word in list order.
The result, in the order words were processed, is ["oath", "eat"].
This runs a full board DFS for every word in the list, retracing shared prefixes once per word. The next approach searches for all words at once: a single DFS from a cell checks against every word at the same time.
Flip the problem. Instead of taking each word and searching the board for it, explore the board and check whether the current path matches any word. A Trie makes this efficient because at each DFS step we follow the corresponding child pointer in the Trie. If no child exists for the current board character, that branch is pruned immediately, since no word in the list starts with this prefix.
Insert all words into a Trie. Then, for each cell on the board, start a DFS. At each cell, check whether the Trie node has a child matching that character. If it does, move to that child and continue exploring in all 4 directions. If the current Trie node marks the end of a word, that word is a match and gets added to the results.
Two optimizations keep this fast:
Removing a found word and pruning empty branches never drops a result that has not been collected yet. A node is deleted only when it has no children and no word marker, so the path through it spells no remaining word and is a prefix of no remaining word. Any later DFS that would have walked into it could only have ended in a dead end. Words still in the Trie remain reachable through their own paths, which are untouched.