Design Add and Search Words Data Structure
Problem statement
Build a class WordDictionary with two operations:
addWord(word)stores a lowercase word.search(pattern)returnstrueif some stored word matchespattern. A pattern is made of lowercase letters and dots, where each.matches exactly one letter of any kind. The match covers the whole word, so the lengths must be equal.
It is a small version of wildcard lookup, like matching web-. against a set of hostnames.
Examples
Example 1
Input: addWord("web"), addWord("db"), addWord("dns"), search("d."), search("..."), search("w.b.")
Output: [true, true, false]
Explanation: d. matches db. ... matches web and dns. No stored word has four letters.
Example 2
Input: addWord("cache"), search("cach"), search("c.c.e")
Output: [false, true]
Explanation: Patterns must match the full word, so cach is not a hit.
Hints
Approach
Trie with a branching search. addWord is an ordinary trie insert that marks the last node as a word end.
search runs match(node, i):
- If
iequals the pattern length, the answer is whethernodeends a word. - If
pattern[i]is a letter, follow that single child (or fail if it's missing). - If it is
., trymatch(child, i + 1)for every child and succeed if any does.
Patterns without dots cost O(L), like a normal trie. Dots multiply the branches, so the worst case is 26^d paths for d dots, but the trie only has branches that real words created, which keeps typical queries far cheaper than scanning every word.
addWord O(L), search O(L) without dots, O(26^d · L) worst caseSpace O(total characters added)class WordDictionary: def __init__(self): self.root = {} def addWord(self, word): node = self.root for ch in word: node = node.setdefault(ch, {}) node["$"] = True def search(self, pattern): def match(node, i): if i == len(pattern): return "$" in node ch = pattern[i] if ch == ".": return any(match(child, i + 1) for key, child in node.items() if key != "$") child = node.get(ch) return child is not None and match(child, i + 1) return match(self.root, 0) d = WordDictionary()for w in ["web", "db", "dns"]: d.addWord(w)print([d.search("d."), d.search("..."), d.search("w.b.")]) d = WordDictionary()d.addWord("cache")print([d.search("cach"), d.search("c.c.e")])Follow-up questions
- Support
*meaning any run of letters, including none. How does the recursion change? - Add
count(pattern)that returns how many stored words match.
Frequently asked questions
Wildcard matching over a set of names is routine infra work: host patterns in inventories, topic wildcards in message brokers, path patterns in routers. This problem is the gentlest version of it and tests whether you can extend a trie search with branching recursion without losing track of the index.
You could turn the pattern into a regex and test every word, but that is the brute force with extra overhead: it still scans the whole dictionary. The trie avoids looking at words whose prefixes already fail to match.
Keep a second index from word length to count (or to a set of words). A pattern that is only dots is then a single lookup. More generally, bucketing tries by word length removes every candidate with the wrong length up front.