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.
Example:
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
2dots inwordforsearchqueries. - At most
104calls will be made toaddWordandsearch.
Approach: Trie with DFS for Wildcards
Algorithm
- Store words in a trie; each node has 26 children and an
isEndflag addWord: insert each character along the trie, marking the last node as endsearch: traverse recursively; when the current character is'.', try every child branch; otherwise follow the specific child. Return true if a full word exists for some branch
Time & Space Complexity
- Time Complexity: O(L) for
addWord; O(26^d * L) worst case forsearch, where d is the number of dots - Space Complexity: O(N * L) - the trie nodes for N words of average length L
Java Implementation
public class WordDictionary {
private static class TrieNode {
TrieNode[] children = new TrieNode[26];
boolean isEnd;
}
private final TrieNode root;
public WordDictionary() {
root = new TrieNode();
}
public void addWord(String word) {
TrieNode node = root;
for (char c : word.toCharArray()) {
int index = c - 'a';
if (node.children[index] == null) {
node.children[index] = new TrieNode();
}
node = node.children[index];
}
node.isEnd = true;
}
public boolean search(String word) {
return search(root, word, 0);
}
private boolean search(TrieNode node, String word, int pos) {
if (node == null) {
return false;
}
if (pos == word.length()) {
return node.isEnd;
}
char c = word.charAt(pos);
if (c == '.') {
for (TrieNode child : node.children) {
if (search(child, word, pos + 1)) {
return true;
}
}
return false;
}
return search(node.children[c - 'a'], word, pos + 1);
}
// Test method
public static void main(String[] args) {
WordDictionary wordDictionary = new WordDictionary();
wordDictionary.addWord("bad");
wordDictionary.addWord("dad");
wordDictionary.addWord("mad");
System.out.println(wordDictionary.search("pad")); // return false
System.out.println(wordDictionary.search("bad")); // return true
System.out.println(wordDictionary.search(".ad")); // return true
System.out.println(wordDictionary.search("b..")); // return true
}
}
Example Walkthrough
After adding “bad”, “dad”, “mad”:
search("pad"): thepchild of the root does not exist -> falsesearch("bad"): followsb -> a -> d, final nodeisEnd = true-> truesearch(".ad"): try all 26 children of root; onlyb,d,mbranches exist, andb.ad,d.ad,m.adall match -> truesearch("b.."): at the second position try all children of nodeb, eventually “bad” matches -> true
Key Points
- Trie Storage: Prefix sharing keeps the dictionary compact and enables character-by-character matching
- Wildcard Branching: A
'.'forces recursion over all 26 children - Recursive Search: The search index tracks progress instead of slicing strings
- End Marker:
isEnddistinguishes complete words from prefixes - Bounded Dots: The constraint of at most 2 dots keeps the exponential factor small