Foundations
Design Add and Search Words Data Structure
Extend a trie with '.' wildcard matching - handle exact characters normally and recurse into all children when a dot is encountered.
Problem
Code editors and fuzzy search tools need to match patterns with wildcards against a dictionary of known words. Design a data structure that supports adding words and searching with patterns where '.' matches any single letter. The challenge is that wildcards require exploring multiple trie branches simultaneously.
Example 1:
Input():
["WordDictionary","addWord","addWord","addWord","search","search","search","search"]
[[],["tan"],["man"],["fun"],["ran"],["tan"],[".an"],["f.."]]
Output: [null,null,null,null,false,true,true,true]
Explanation: "ran" was never added, "tan" matches exactly, and "." matches any single letter.Example 2:
add("tan"), add("man"), add("fun")
search("....")
Output: False
Explanation: no 4-letter words were inserted, so the 4-character pattern cannot match.Constraints:
wordinaddWordconsists of lowercase English letters.wordinsearchconsists of'.'or lowercase English letters.- There will be at most
3dots inwordforsearchqueries. - At most
5000calls will be made toaddWordandsearch. 1 <= word.length <= 20
Solution Breakdown
Approach: Standard trie insert plus a recursive DFS that branches at every . wildcard.
add_word is an ordinary trie insert - walk the word, create missing nodes, mark is_end = True on the last one. All the difficulty is in search, which delegates to a recursive helper _search(node, index) carrying the current trie node and position in the pattern. The base case fires when index == len(word): the whole pattern has been consumed, so the answer is node.is_end - a real word must terminate exactly here. For a concrete character it behaves like a plain trie walk: if the character has no child, return False; otherwise recurse into node.children[char] at index + 1. The . case is what an iterative loop cannot express - the wildcard could match any present child, so it recurses into every child with any(_search(child, index + 1) for child in node.children.values()), returning True the moment one branch succeeds. Trace search("t..") with tan and tin inserted: match t exactly, then the first . recurses into the only child a, then the second . recurses into both n and i, and each is a complete word, so the search returns True.
Edge cases: A . at a node with no children makes the any(...) iterate an empty collection and return False. A pattern longer than any stored word fails because traversal runs out of children before the base case. Recursion always terminates since index strictly increases each call.
Complexity: O(m) time for add_word and for a wildcard-free search; worst case O(26^m * m) when the pattern is all dots, since each dot fans out to up to 26 children across m levels. O(total characters) space for the trie plus O(m) recursion depth.
Done reading? Mark it so it sticks in your dashboard.