Medium
Subtopics
depth-first-search·design·string·trie
Companies
airbnb·amazon·apple·ebay·facebook·google·microsoft·salesforce·uberLast updated: Oct 16, 2025
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) Adds word to the data structure, it can be matched later.
bool search(word) Returns true if there is any string in the data structure that matches word or false otherwise. word may contain dots '.' where dots can be matched with any letter.
Keep every added word in a plain list (or array). To answer a search(pattern) we test the pattern against each stored word. The wildcard . only affects per‑character comparison; no preprocessing is needed. This is the simplest design but scales poorly when the number of words grows.
classWordDictionary {
privatefinal List<String> words =new ArrayList<>();
publicWordDictionary() {}
publicvoidaddWord(String word) {
words.add(word);
}
publicbooleansearch(String pattern) {
int L = pattern.length();
outer: for (String w : words) {
if (w.length() != L) continue;
for (int i = 0; i < L; i++) {
char pc = pattern.charAt(i);
if (pc !='.'&& pc != w.charAt(i)) {
continue outer;
}
}
returntrue;
}
returnfalse;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
classWordDictionary:
def__init__(self) ->None:
self.words: list[str] = []
defaddWord(self, word: str) ->None:
self.words.append(word)
defsearch(self, pattern: str) -> bool:
L = len(pattern)
for w in self.words:
if len(w) != L:
continuefor i in range(L):
if pattern[i] !='.'and pattern[i] != w[i]:
breakelse:
returnTruereturnFalse
Method 2 - Trie with Map Children (Substring-Based DFS)#
Instead of re-checking unrelated words, a Trie groups words by shared prefixes. Each node represents a prefix; descending one character at a time prunes huge portions of the search space. The wildcard . forces branching: we must explore all child nodes at that position. This version uses word.substring(1) at each recursive step for simplicity, trading speed/memory for cleaner code.
⏰ Time complexity:addWord: O(L) traverse/create nodes; search exact: O(L) follow one path; search pattern: O(b^k * L) worst case where k = number of '.' wildcard positions in the query (each can branch to up to b ≤ 26 children). Substring creation adds O(L) per level yielding O(b^k * L^2) in the pathological case.
🧺 Space complexity:O(N * L) nodes across all inserted words (one per unique prefix) plus recursion stack O(L) and transient substrings up to O(L^2) worst case.
publicclassWordDictionary {
staticclassTrieNode {
Map<Character, TrieNode> children =new HashMap<Character, TrieNode> ();
boolean isEndOfWord;
}
privatefinal TrieNode root;
publicWordDictionary() {
root =new TrieNode();
}
// Adds a word into the data structure.publicvoidaddWord(String word) {
TrieNode curr = root;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
if (!curr.children.containsKey(c)) {
curr.children.put(c, new TrieNode());
}
curr = curr.children.get(c);
}
curr.isEndOfWord=true;
}
// Returns if the word is in the data structure. A word could// contain the dot character '.' to represent any one letter.publicbooleansearch(String word) {
return searchInNode(word, root);
}
privatebooleansearchInNode(String word, TrieNode node) {
if (word ==null|| (word.isEmpty())) {
return node.isEndOfWord;
}
char c = word.charAt(0);
if (c =='.') {
for (TrieNode child : node.children.values()) {
if (searchInNode(word.substring(1), child)) {
returntrue;
}
}
}
//if character is neither equal to the node nor '.', return falseif (!node.children.containsKey(c)) {
returnfalse;
}
return searchInNode(word.substring(1), node.children.get(c));
}
}
Method 2 pays an extra cost creating substring objects at every recursion level. We can avoid that by passing an index into the original pattern string, reducing both time and memory overhead. Logic is identical; only parameterization changes.
addWord: O(L) as insertion is simple down the path;
search exact: O(L);
search pattern: O(N * L) as we may have to go through whole of trie, but if the trie is complete, then it can be O(a^L), where is alphabet size, which is 26 for this problem.
🧺 Space complexity:O(N * L) for trie nodes; recursion stack O(L); auxiliary O(1) per frame (no substring allocations).
publicclassWordDictionary {
staticclassTrieNode {
Map<Character, TrieNode> children =new HashMap<Character, TrieNode> ();
boolean isEndOfWord;
}
privatefinal TrieNode root;
publicWordDictionary() {
root =new TrieNode();
}
// Adds a word into the data structure.publicvoidaddWord(String word) {
TrieNode curr = root;
for (int i = 0; i < word.length(); i++) {
char c = word.charAt(i);
if (!curr.children.containsKey(c)) {
curr.children.put(c, new TrieNode());
}
curr = curr.children.get(c);
}
curr.isEndOfWord=true;
}
// Returns if the word is in the data structure. A word could// contain the dot character '.' to represent any one letter.publicbooleansearch(String word) {
return searchInNode(word, 0, root);
}
privatebooleansearchInNode(String word, int index, TrieNode node) {
if (index == word.length()) {
return node.isEndOfWord;
}
char c = word.charAt(index);
if (c =='.') {
for (TrieNode child : node.children.values()) {
if (searchInNode(word, index+1, child)) {
returntrue;
}
}
returnfalse;
}
//if character at indexition 'index' is neither equal to the node nor '.', return falseif (!node.children.containsKey(word.charAt(index))) {
returnfalse;
}
return searchInNode(word, index + 1, node.children.get(c));
}
}
When the alphabet is known and small (lowercase English letters), a fixed array of size 26 per node replaces the hash map. This removes hash overhead and can speed up traversal at the cost of allocating 26 slots per node (even if sparse). Memory trade‑off vs Method 2/3: faster constant factors, higher baseline space for few children.
addWord: O(L) as insertion is simple down the path;
search exact: O(L);
search pattern: O(N * L) as we may have to go through whole of trie, but if the trie is complete, then it can be O(a^L), where is alphabet size, which is 26 for this problem.
🧺 Space complexity:O(N * L) for trie nodes; recursion stack O(L); auxiliary O(1) per frame (no substring allocations).