Facebook Pixel

211. Design Add and Search Words Data Structure

Problem Description

This problem asks you to design a data structure called WordDictionary that can store words and search for them with a special wildcard feature.

The data structure needs to support two operations:

  1. addWord(word): Adds a word to the dictionary. The word consists only of lowercase English letters.

  2. search(word): Searches for a word in the dictionary. The search query can contain:

    • Regular lowercase English letters that must match exactly
    • The wildcard character '.' which can match any single letter

For example, if you add the words "bad", "dad", and "mad" to the dictionary:

  • Searching for "bad" returns True (exact match exists)
  • Searching for "pad" returns False (no exact match)
  • Searching for ".ad" returns True (matches "bad", "dad", and "mad" where '.' can be 'b', 'd', or 'm')
  • Searching for "b.." returns True (matches "bad" where the two dots match 'a' and 'd')

The solution uses a Trie (prefix tree) data structure to efficiently store and search words. Each node in the Trie has 26 children (one for each lowercase letter) and a flag indicating if it marks the end of a valid word.

The addWord operation traverses the Trie, creating nodes as needed for each character in the word, and marks the final node as a word ending.

The search operation uses recursion to handle the wildcard character '.'. When encountering a dot, it explores all possible branches in the Trie at that position. For regular characters, it follows the specific branch if it exists. The search succeeds if it reaches a node marked as a word ending after processing all characters.

Flowchart Walkthrough

First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:

Is it a graph?

  • Yes: The Trie data structure is essentially a tree, which is a special type of graph. Each node represents a prefix, and edges represent character transitions.

Is it a tree?

  • Yes: A Trie is specifically a tree structure where each node can have up to 26 children (one for each lowercase letter), and there's a single root node.

DFS

  • Yes: The search operation, especially when handling the wildcard character '.', requires exploring multiple branches of the tree. When we encounter a '.', we need to recursively explore all possible child nodes, which is a classic depth-first search pattern.

Conclusion: The flowchart leads us to use DFS (Depth-First Search) for this problem. This makes perfect sense because:

  1. The Trie is a tree structure that we need to traverse
  2. The wildcard search requires exploring multiple paths when encountering '.'
  3. DFS with backtracking allows us to explore each possible branch recursively and return true if any path matches the search pattern
  4. The recursive nature of DFS perfectly handles the variable depth traversal needed for words of different lengths
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

When we need to store and search for words efficiently, especially with pattern matching, we should think about how words share common prefixes. For instance, "bad", "bat", and "ban" all start with "ba". Instead of storing each word separately, we can build a tree-like structure where common prefixes are shared.

This naturally leads us to a Trie (prefix tree), where each path from root to a leaf represents a word. Each node stores whether it marks the end of a valid word, and has up to 26 children (one for each letter).

The real challenge comes with the wildcard search using '.'. When we encounter a regular letter, we simply follow the corresponding branch if it exists. But when we hit a '.', we need to explore all possible branches at that level since '.' can match any letter.

This "explore all possibilities" requirement screams for a recursive DFS approach. At each '.', we branch out to all existing children and continue searching from each one. If any branch leads to a successful match, we return True. This is essentially a backtracking pattern - we try one path, and if it doesn't work, we backtrack and try another.

The beauty of this approach is that the recursive DFS naturally handles the variable depth of the search (words of different lengths) and the branching factor when encountering wildcards. The recursion stack keeps track of our current position in both the search word and the Trie, making the implementation clean and intuitive.

For example, when searching for "b.." in a Trie containing "bad", we start at root, move to 'b', then at the first '.' we find child 'a' exists, move there, then at the second '.' we find child 'd' exists and it's marked as a word ending - success!

Solution Approach

The implementation consists of two classes: Trie and WordDictionary.

Trie Node Structure: Each Trie node contains:

  • children: An array of 26 elements (for 'a' to 'z'), where children[i] points to the child node for character chr(ord('a') + i)
  • is_end: A boolean flag indicating if this node marks the end of a valid word

Adding Words (addWord):

  1. Start from the root of the Trie
  2. For each character c in the word:
    • Calculate its index: idx = ord(c) - ord('a')
    • If node.children[idx] doesn't exist, create a new Trie node there
    • Move to the child node: node = node.children[idx]
  3. After processing all characters, mark the final node with node.is_end = True

Searching with Wildcards (search): The search uses a recursive helper function that takes the remaining word to search and the current node:

  1. Iterate through each character in the word:

    • If it's a regular letter and the corresponding child doesn't exist, return False
    • If it's a '.' wildcard:
      • Try all non-null children recursively
      • For each child, search the remaining substring word[i+1:] starting from that child
      • If any recursive call returns True, we found a match
      • If no child leads to a match, return False
    • If it's a regular letter with an existing child, move to that child
  2. After processing all characters, return node.is_end to check if we've reached a valid word ending

Key Implementation Details:

  • The wildcard handling uses DFS to explore all possible branches when encountering '.'
  • The recursion naturally handles backtracking - if one branch fails, we try the next
  • String slicing word[i+1:] passes the remaining portion of the word to recursive calls
  • The base case occurs when we've processed all characters and check is_end

Time Complexity:

  • addWord: O(m) where m is the length of the word
  • search: O(n ร— 26^k) worst case, where n is the word length and k is the number of dots (since each dot requires exploring up to 26 branches)

Space Complexity:

  • O(ALPHABET_SIZE ร— N ร— M) for storing the Trie, where N is the number of words and M is the average length

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example to see how the Trie-based solution works.

Step 1: Initialize and Add Words

Start with an empty WordDictionary and add three words: "bad", "dad", "mad"

Initial: root = Trie()

Adding "bad":
- Start at root
- 'b': Create child at index 1 (b-a=1), move there
- 'a': Create child at index 0 (a-a=0), move there  
- 'd': Create child at index 3 (d-a=3), move there
- Mark is_end = True

Adding "dad":
- Start at root
- 'd': Create child at index 3 (d-a=3), move there
- 'a': Create child at index 0, move there
- 'd': Create child at index 3, move there  
- Mark is_end = True

Adding "mad":
- Start at root
- 'm': Create child at index 12 (m-a=12), move there
- 'a': Create child at index 0, move there
- 'd': Create child at index 3, move there
- Mark is_end = True

The resulting Trie structure looks like:

       root
      / | \
    b   d   m
    |   |   |
    a   a   a
    |   |   |
    d*  d*  d*
  
(* indicates is_end = True)

Step 2: Search for ".ad"

Now let's search for the pattern ".ad" which should return True:

  1. Start at root with word = ".ad", index = 0

  2. First character is '.', so we need to try all non-null children of root

  3. Root has three children at indices 1('b'), 3('d'), and 12('m')

    Branch 1: Try child 'b'

    • Recursively search for "ad" starting from the 'b' node
    • Next char 'a': Check children[0] of 'b' node โ†’ exists, move there
    • Next char 'd': Check children[3] of 'a' node โ†’ exists, move there
    • Reached end of pattern, check is_end โ†’ True
    • Return True (found "bad")

Since we found a match, the search returns True immediately without needing to check the other branches.

Step 3: Search for "b.."

Let's trace through searching for "b..":

  1. Start at root with word = "b..", index = 0
  2. First character is 'b': Check children[1] โ†’ exists, move to 'b' node
  3. Second character is '.': Try all non-null children of 'b' node
    • 'b' node only has one child at index 0 ('a')
    Branch: Try child 'a'
    • Recursively search for "." starting from 'a' node
    • Next char is '.': Try all non-null children of 'a' node
    • 'a' node only has one child at index 3 ('d')
    Sub-branch: Try child 'd'
    • Recursively search for "" (empty) starting from 'd' node
    • No more characters to process
    • Check is_end of 'd' node โ†’ True
    • Return True (found "bad")

Step 4: Search for "b.d" (False case)

  1. Start at root with word = "b.d", index = 0
  2. First character is 'b': Check children[1] โ†’ exists, move to 'b' node
  3. Second character is '.': Try all non-null children of 'b' node
    • Only child is at index 0 ('a'), so recursively search for "d" from 'a' node
  4. From 'a' node, look for 'd': Check children[3] โ†’ exists, move to 'd' node
  5. Reached end of pattern, check is_end โ†’ True
  6. Return True (found "bad")

Wait, this should return True! Let me trace a false case instead:

Step 4 (corrected): Search for "ba" (False case)

  1. Start at root with word = "ba", index = 0
  2. First character is 'b': Check children[1] โ†’ exists, move to 'b' node
  3. Second character is 'a': Check children[0] โ†’ exists, move to 'a' node
  4. Reached end of pattern, check is_end of 'a' node โ†’ False (not a word ending)
  5. Return False

This returns False because although the path "ba" exists in our Trie, it's not marked as a complete word - only "bad" is marked as complete.

Solution Implementation

1class TrieNode:
2    """Represents a node in the Trie data structure."""
3  
4    def __init__(self):
5        # Array to store references to child nodes (26 letters a-z)
6        self.children = [None] * 26
7        # Flag to mark if this node represents the end of a word
8        self.is_end_of_word = False
9
10
11class WordDictionary:
12    """
13    Data structure that supports adding words and searching with wildcard '.' support.
14    Uses a Trie (prefix tree) for efficient storage and retrieval.
15    """
16  
17    def __init__(self):
18        """Initialize the WordDictionary with an empty Trie."""
19        self.root = TrieNode()
20
21    def addWord(self, word: str) -> None:
22        """
23        Add a word to the dictionary.
24      
25        Args:
26            word: The word to be added (contains only lowercase letters)
27        """
28        current_node = self.root
29      
30        # Traverse through each character in the word
31        for char in word:
32            # Calculate the index for this character (0-25 for a-z)
33            index = ord(char) - ord('a')
34          
35            # Create a new node if path doesn't exist
36            if current_node.children[index] is None:
37                current_node.children[index] = TrieNode()
38          
39            # Move to the child node
40            current_node = current_node.children[index]
41      
42        # Mark the end of the word
43        current_node.is_end_of_word = True
44
45    def search(self, word: str) -> bool:
46        """
47        Search for a word in the dictionary.
48        Supports '.' as a wildcard that can match any single character.
49      
50        Args:
51            word: The word pattern to search for
52          
53        Returns:
54            True if the word exists in the dictionary, False otherwise
55        """
56      
57        def dfs_search(word_substring: str, node: TrieNode) -> bool:
58            """
59            Helper function to recursively search with wildcard support.
60          
61            Args:
62                word_substring: Remaining part of the word to search
63                node: Current node in the Trie
64              
65            Returns:
66                True if pattern matches, False otherwise
67            """
68            # Process each character in the remaining word
69            for i in range(len(word_substring)):
70                char = word_substring[i]
71              
72                if char == '.':
73                    # Wildcard: try all possible children
74                    for child_node in node.children:
75                        if child_node is not None:
76                            # Recursively search with remaining substring
77                            if dfs_search(word_substring[i + 1:], child_node):
78                                return True
79                    # No valid path found through any child
80                    return False
81                else:
82                    # Regular character: check if path exists
83                    index = ord(char) - ord('a')
84                    if node.children[index] is None:
85                        return False
86                    # Move to the next node
87                    node = node.children[index]
88          
89            # Check if we've reached a valid word ending
90            return node.is_end_of_word
91      
92        # Start search from the root
93        return dfs_search(word, self.root)
94
95
96# Your WordDictionary object will be instantiated and called as such:
97# obj = WordDictionary()
98# obj.addWord(word)
99# param_2 = obj.search(word)
100
1/**
2 * Trie node structure for storing words
3 * Each node contains an array of 26 children (for lowercase letters a-z)
4 * and a boolean flag to mark the end of a word
5 */
6class Trie {
7    Trie[] children = new Trie[26];
8    boolean isEnd;
9}
10
11/**
12 * Data structure that supports adding words and searching with wildcard '.'
13 * Uses a Trie (prefix tree) for efficient storage and retrieval
14 */
15class WordDictionary {
16    private Trie root;
17
18    /**
19     * Initialize the WordDictionary with an empty Trie
20     */
21    public WordDictionary() {
22        root = new Trie();
23    }
24
25    /**
26     * Adds a word to the data structure
27     * @param word The word to be added (contains only lowercase letters)
28     */
29    public void addWord(String word) {
30        Trie currentNode = root;
31      
32        // Traverse through each character in the word
33        for (char ch : word.toCharArray()) {
34            int index = ch - 'a';
35          
36            // Create a new node if path doesn't exist
37            if (currentNode.children[index] == null) {
38                currentNode.children[index] = new Trie();
39            }
40          
41            // Move to the next node
42            currentNode = currentNode.children[index];
43        }
44      
45        // Mark the end of the word
46        currentNode.isEnd = true;
47    }
48
49    /**
50     * Search for a word in the data structure
51     * @param word The word to search (may contain '.' as wildcard for any letter)
52     * @return true if the word exists, false otherwise
53     */
54    public boolean search(String word) {
55        return searchHelper(word, root);
56    }
57
58    /**
59     * Helper method for recursive search with wildcard support
60     * @param word The word or remaining substring to search
61     * @param currentNode The current Trie node being examined
62     * @return true if the word pattern matches, false otherwise
63     */
64    private boolean searchHelper(String word, Trie currentNode) {
65        // Process each character in the word
66        for (int i = 0; i < word.length(); i++) {
67            char ch = word.charAt(i);
68          
69            if (ch == '.') {
70                // Wildcard case: try all possible children
71                for (Trie childNode : currentNode.children) {
72                    if (childNode != null && 
73                        searchHelper(word.substring(i + 1), childNode)) {
74                        return true;
75                    }
76                }
77                return false;
78            } else {
79                // Regular character case
80                int index = ch - 'a';
81              
82                // If no child exists for this character, word doesn't exist
83                if (currentNode.children[index] == null) {
84                    return false;
85                }
86              
87                // Move to the next node
88                currentNode = currentNode.children[index];
89            }
90        }
91      
92        // Check if we've reached a valid word ending
93        return currentNode.isEnd;
94    }
95}
96
97/**
98 * Your WordDictionary object will be instantiated and called as such:
99 * WordDictionary obj = new WordDictionary();
100 * obj.addWord(word);
101 * boolean param_2 = obj.search(word);
102 */
103
1/**
2 * Trie (Prefix Tree) Node Implementation
3 * Used to store words character by character in a tree structure
4 */
5class TrieNode {
6public:
7    // Array to store pointers to child nodes (26 for lowercase English letters)
8    vector<TrieNode*> children;
9    // Flag to mark if this node represents the end of a word
10    bool isEndOfWord;
11  
12    // Constructor initializes children array and end flag
13    TrieNode() {
14        children = vector<TrieNode*>(26, nullptr);
15        isEndOfWord = false;
16    }
17  
18    // Insert a word into the trie starting from this node
19    void insert(const string& word) {
20        TrieNode* currentNode = this;
21      
22        // Traverse each character in the word
23        for (char ch : word) {
24            // Convert character to index (0-25)
25            int index = ch - 'a';
26          
27            // Create new node if path doesn't exist
28            if (currentNode->children[index] == nullptr) {
29                currentNode->children[index] = new TrieNode();
30            }
31          
32            // Move to the child node
33            currentNode = currentNode->children[index];
34        }
35      
36        // Mark the last node as end of word
37        currentNode->isEndOfWord = true;
38    }
39};
40
41/**
42 * Design a data structure that supports adding new words and finding if a string matches
43 * any previously added string. Supports wildcard '.' that can match any single character.
44 */
45class WordDictionary {
46private:
47    TrieNode* root;  // Root node of the trie
48  
49    /**
50     * Depth-first search helper function to handle wildcard matching
51     * @param word - The word pattern to search
52     * @param index - Current position in the word being processed
53     * @param currentNode - Current trie node being examined
54     * @return true if pattern matches, false otherwise
55     */
56    bool dfsSearch(const string& word, int index, TrieNode* currentNode) {
57        // Base case: reached end of word
58        if (index == word.size()) {
59            return currentNode->isEndOfWord;
60        }
61      
62        char currentChar = word[index];
63      
64        // Case 1: Regular character (not wildcard)
65        if (currentChar != '.') {
66            int childIndex = currentChar - 'a';
67            TrieNode* childNode = currentNode->children[childIndex];
68          
69            // Continue search if child exists
70            if (childNode != nullptr && dfsSearch(word, index + 1, childNode)) {
71                return true;
72            }
73        }
74        // Case 2: Wildcard character '.'
75        else {
76            // Try all possible children
77            for (TrieNode* childNode : currentNode->children) {
78                if (childNode != nullptr && dfsSearch(word, index + 1, childNode)) {
79                    return true;
80                }
81            }
82        }
83      
84        return false;
85    }
86  
87public:
88    // Constructor initializes the root node
89    WordDictionary() : root(new TrieNode()) {}
90  
91    /**
92     * Add a word to the data structure
93     * @param word - The word to be added (contains only lowercase letters)
94     */
95    void addWord(string word) {
96        root->insert(word);
97    }
98  
99    /**
100     * Search if there is any word in the data structure that matches the given pattern
101     * @param word - The pattern to search (may contain '.' as wildcard)
102     * @return true if pattern matches any word, false otherwise
103     */
104    bool search(string word) {
105        return dfsSearch(word, 0, root);
106    }
107};
108
109/**
110 * Your WordDictionary object will be instantiated and called as such:
111 * WordDictionary* obj = new WordDictionary();
112 * obj->addWord(word);
113 * bool param_2 = obj->search(word);
114 */
115
1/**
2 * Trie (Prefix Tree) Node Implementation
3 * Used to store words character by character in a tree structure
4 */
5class TrieNode {
6    // Array to store pointers to child nodes (26 for lowercase English letters)
7    children: (TrieNode | null)[];
8    // Flag to mark if this node represents the end of a word
9    isEndOfWord: boolean;
10  
11    // Constructor initializes children array and end flag
12    constructor() {
13        this.children = new Array(26).fill(null);
14        this.isEndOfWord = false;
15    }
16  
17    /**
18     * Insert a word into the trie starting from this node
19     * @param word - The word to be inserted
20     */
21    insert(word: string): void {
22        let currentNode: TrieNode = this;
23      
24        // Traverse each character in the word
25        for (const char of word) {
26            // Convert character to index (0-25)
27            const index = char.charCodeAt(0) - 'a'.charCodeAt(0);
28          
29            // Create new node if path doesn't exist
30            if (currentNode.children[index] === null) {
31                currentNode.children[index] = new TrieNode();
32            }
33          
34            // Move to the child node
35            currentNode = currentNode.children[index]!;
36        }
37      
38        // Mark the last node as end of word
39        currentNode.isEndOfWord = true;
40    }
41}
42
43// Root node of the trie
44let root: TrieNode;
45
46/**
47 * Depth-first search helper function to handle wildcard matching
48 * @param word - The word pattern to search
49 * @param index - Current position in the word being processed
50 * @param currentNode - Current trie node being examined
51 * @returns true if pattern matches, false otherwise
52 */
53function dfsSearch(word: string, index: number, currentNode: TrieNode): boolean {
54    // Base case: reached end of word
55    if (index === word.length) {
56        return currentNode.isEndOfWord;
57    }
58  
59    const currentChar = word[index];
60  
61    // Case 1: Regular character (not wildcard)
62    if (currentChar !== '.') {
63        const childIndex = currentChar.charCodeAt(0) - 'a'.charCodeAt(0);
64        const childNode = currentNode.children[childIndex];
65      
66        // Continue search if child exists
67        if (childNode !== null && dfsSearch(word, index + 1, childNode)) {
68            return true;
69        }
70    }
71    // Case 2: Wildcard character '.'
72    else {
73        // Try all possible children
74        for (const childNode of currentNode.children) {
75            if (childNode !== null && dfsSearch(word, index + 1, childNode)) {
76                return true;
77            }
78        }
79    }
80  
81    return false;
82}
83
84/**
85 * Initialize the WordDictionary data structure
86 */
87function WordDictionary(): void {
88    root = new TrieNode();
89}
90
91/**
92 * Add a word to the data structure
93 * @param word - The word to be added (contains only lowercase letters)
94 */
95function addWord(word: string): void {
96    root.insert(word);
97}
98
99/**
100 * Search if there is any word in the data structure that matches the given pattern
101 * @param word - The pattern to search (may contain '.' as wildcard)
102 * @returns true if pattern matches any word, false otherwise
103 */
104function search(word: string): boolean {
105    return dfsSearch(word, 0, root);
106}
107
108/**
109 * Your WordDictionary object will be instantiated and called as such:
110 * WordDictionary();
111 * addWord(word);
112 * let param_2 = search(word);
113 */
114

Time and Space Complexity

Time Complexity:

  • addWord(word): O(m) where m is the length of the word being added. We traverse through each character of the word exactly once, and for each character, we perform constant time operations (array indexing and node creation).

  • search(word):

    • Best case: O(m) where m is the length of the search word, when there are no wildcard characters ('.'). We simply traverse down the trie following the exact path.
    • Worst case: O(n ร— 26^m) where m is the length of the search word and n is the total number of nodes in the trie. This occurs when the word consists entirely of wildcards ('.'). At each '.', we potentially explore all 26 branches, and for each branch, we recursively search the remaining substring. In the absolute worst case with all dots, we could explore 26^m different paths.
    • Average case with wildcards: O(n ร— 26^k) where k is the number of wildcard characters in the search word.

Space Complexity:

  • addWord(word): O(m) in the worst case where m is the length of the word. If none of the characters in the word share a common prefix with existing words, we create m new Trie nodes.

  • search(word): O(m) for the recursion stack depth, where m is the length of the search word. In the worst case with wildcards, the recursive calls can go as deep as the length of the word.

  • Overall space complexity of the data structure: O(ALPHABET_SIZE ร— N ร— M) = O(26 ร— N ร— M) where N is the total number of words inserted and M is the average length of the words. Each Trie node contains an array of 26 pointers to children nodes. In the worst case where no words share common prefixes, we would have N ร— M nodes, each with a 26-element array.

Common Pitfalls

1. Inefficient String Slicing in Recursion

The most common performance pitfall in this solution is the use of string slicing word_substring[i + 1:] during recursive calls. This creates a new string object every time, leading to O(n) extra time and space complexity for each recursive call.

Problem Example:

# Current inefficient approach
if dfs_search(word_substring[i + 1:], child_node):  # Creates new string
    return True

Solution: Use an index pointer instead of string slicing:

def search(self, word: str) -> bool:
    def dfs_search(index: int, node: TrieNode) -> bool:
        # Base case: reached end of word
        if index == len(word):
            return node.is_end_of_word
      
        char = word[index]
      
        if char == '.':
            # Wildcard: try all possible children
            for child_node in node.children:
                if child_node is not None:
                    # Pass index+1 instead of slicing
                    if dfs_search(index + 1, child_node):
                        return True
            return False
        else:
            # Regular character
            char_index = ord(char) - ord('a')
            if node.children[char_index] is None:
                return False
            return dfs_search(index + 1, node.children[char_index])
  
    return dfs_search(0, self.root)

2. Forgetting to Handle Empty String Edge Case

The code doesn't explicitly handle the case where an empty string is added or searched. While the current implementation technically works (empty string returns True only if root.is_end_of_word is True), it's better to be explicit.

Solution: Add explicit checks:

def addWord(self, word: str) -> None:
    if not word:  # Handle empty string
        self.root.is_end_of_word = True
        return
    # ... rest of the implementation

def search(self, word: str) -> bool:
    if not word:  # Handle empty string
        return self.root.is_end_of_word
    # ... rest of the implementation

3. Not Optimizing the Wildcard Search

When encountering a wildcard, the current approach iterates through all 26 children even if many are None. This can be optimized by maintaining a list of actual children or checking early termination conditions.

Solution: Early termination and optimization:

def dfs_search(index: int, node: TrieNode) -> bool:
    if index == len(word):
        return node.is_end_of_word
  
    char = word[index]
  
    if char == '.':
        # Early termination: if this is the last character
        if index == len(word) - 1:
            # Just check if any child has is_end_of_word = True
            for child in node.children:
                if child and child.is_end_of_word:
                    return True
            return False
      
        # Continue searching for non-last characters
        for child in node.children:
            if child and dfs_search(index + 1, child):
                return True
        return False
    else:
        char_index = ord(char) - ord('a')
        if node.children[char_index] is None:
            return False
        return dfs_search(index + 1, node.children[char_index])

4. Memory Waste with Fixed-Size Arrays

Using [None] * 26 for every node wastes memory when words share few common prefixes or when the dictionary is sparse.

Solution: Use a dictionary instead of a fixed array:

class TrieNode:
    def __init__(self):
        self.children = {}  # Use dictionary instead of array
        self.is_end_of_word = False

def addWord(self, word: str) -> None:
    current_node = self.root
    for char in word:
        if char not in current_node.children:
            current_node.children[char] = TrieNode()
        current_node = current_node.children[char]
    current_node.is_end_of_word = True

These optimizations significantly improve both time and space efficiency, especially for large datasets with many wildcard searches.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which data structure is used in a depth first search?


Recommended Readings

Want a Structured Path to Master System Design Too? Donโ€™t Miss This!

Load More