[Medium] 211. Design Add and Search Words Data Structure
Design a data structure that supports adding new words and finding if a string matches any previously added string.
Implement the WordDictionary class:
WordDictionary()Initializes the object.void addWord(word)Addswordto the data structure, it can be matched later.bool search(word)Returnstrueif there is any string in the data structure that matcheswordorfalseotherwise.wordmay contain dots'.'where dots can be matched with any letter.
Examples
Example 1:
Input
["WordDictionary","addWord","addWord","addWord","search","search","search","search"]
[[],["bad"],["dad"],["mad"],["pad"],["bad"],[".ad"],["b.."]]
Output
[null,null,null,null,false,true,true,true]
Explanation
WordDictionary wordDictionary = new WordDictionary();
wordDictionary.addWord("bad");
wordDictionary.addWord("dad");
wordDictionary.addWord("mad");
wordDictionary.search("pad"); // return False
wordDictionary.search("bad"); // return True
wordDictionary.search(".ad"); // return True
wordDictionary.search("b.."); // return True
Constraints
1 <= word.length <= 25wordinaddWordconsists of lowercase English letters.wordinsearchconsist of'.'or lowercase English letters.- There will be at most
10^4calls toaddWordandsearch.
Thinking Process
- Trie Foundation: Builds on standard Trie structure
- DFS explores one branch fully before backtracking.
- Mark visited nodes to avoid cycles on graphs.
- Return aggregated results from children to the parent.
Common Approaches
Typical techniques for this pattern:
| Approach | Time | Space | Notes |
|---|---|---|---|
| Recursive DFS (this problem) | O(n) | O(h) stack | Natural for trees and graphs |
| Iterative DFS (stack) | O(n) | O(n) | Avoid recursion depth limits |
| DFS with memoization | O(n) | O(n) | Overlapping subproblems on graphs |
| Backtracking DFS | O(2^n) typical | O(n) | Enumerate choices with pruning |
Solution
struct TrieNode :
dict[char, TrieNode> children
bool isWord = False
class WordDictionary:
WordDictionary() :
root = new TrieNode()
~WordDictionary() :
deleteTrie(root)
def addWord(self, word):
TrieNode node = root
for ch in word:
if not node.ch in children:
node.children[ch] = new TrieNode()
node = node.children[ch]
node.isWord = True
def search(self, word):
return searchInNode(word, 0, root)
TrieNode root
def searchInNode(self, word, idx, node):
if(not node) return False
if(idx == len(word)) return node.isWord
char curr = word[idx]
if curr == '.':
for([_, child]: node.children) :
if searchInNode(word, idx + 1, child):
return True
return False
if(not node.curr in children) return False
return searchInNode(word, idx + 1, node.children[curr])
def deleteTrie(self, node):
if(not node) return
for([_, child]: node.children) :
deleteTrie(child)
delete node
/
Your WordDictionary object will be instantiated and called as such:
WordDictionary obj = new WordDictionary()
obj.addWord(word)
bool param_2 = obj.search(word)
/
Solution Explanation
Approach: Recursive DFS (this problem)
Key idea: 1. Trie Foundation: Builds on standard Trie structure
How the code works:
- Trie Foundation: Builds on standard Trie structure
- DFS explores one branch fully before backtracking.
- Mark visited nodes to avoid cycles on graphs.
- Return aggregated results from children to the parent.
Common Mistakes
- Single wildcard:
search(".")when no single-letter words exist - All wildcards:
search("...")explores all 3-letter words - Wildcard at start:
search(".ad")matches “bad”, “dad”, “mad” - Wildcard at end:
search("ba.")matches “bad”, “bat”, etc. - No match:
search("xyz")returns false correctly - Empty word: Not applicable (constraints guarantee length ≥ 1)
-
Multiple wildcards:
search("b..")handles multiple wildcards - Not handling null nodes: Forgetting to check if node exists before accessing
- Incorrect base case: Not checking
idx == word.size()before checkingisWord - Wildcard logic: Not trying all children when encountering
'.' - Memory leaks: Forgetting to implement destructor
- Early exit: Not returning immediately when match found in wildcard search
- Index management: Off-by-one errors in recursive calls
When to Use This Structure
- Word Search with Wildcards: Pattern matching in dictionaries
- Autocomplete with Partial Input: When users type incomplete words
- Spell Checker: Finding similar words with wildcards
- Text Search: Searching documents with pattern matching
- Game Development: Word games like Scrabble, Boggle
- Search Engines: Prefix-based search with pattern support
Related Problems
- LC 208: Implement Trie (Prefix Tree) - Basic Trie without wildcards
- LC 212: Word Search II - Trie + DFS on board
- LC 642: Design Search Autocomplete System - Trie with frequency tracking
- LC 648: Replace Words - Trie for prefix replacement
- LC 677: Map Sum Pairs - Trie with value storage
- LC 720: Longest Word in Dictionary - Trie traversal
This problem extends the Trie data structure with wildcard search capabilities. The key is using DFS to explore all possible paths when encountering wildcard characters, making it useful for pattern matching and flexible word search applications.
Key Takeaways
- Trie Foundation: Builds on standard Trie structure
- Wildcard Handling: DFS explores all paths when encountering
'.' - Early Termination: Returns true immediately when match found
- Memory Safety: Destructor prevents memory leaks
- Map vs Array: Using
unordered_mapis more flexible but slightly slower than array - Recursive Search: Clean recursive approach for wildcard matching
References
- LC 211: Design Add and Search Words Data Structure on LeetCode
- LeetCode Discuss — LC 211: Design Add and Search Words Data Structure
- LeetCode Editorial (may require premium)