LeetCode Problem Workspace

Implement Magic Dictionary

Design a Magic Dictionary to allow searching with one-character modifications, using Hash Table and String techniques.

category

5

Topics

code_blocks

6

Code langs

hub

3

Related

Practice Focus

Medium · Hash Table plus String

bolt

Answer-first summary

Design a Magic Dictionary to allow searching with one-character modifications, using Hash Table and String techniques.

Interview AiBox logo

Ace coding interviews with Interview AiBox guidance for Hash Table plus String

Try AiBox Copilotarrow_forward

This problem requires designing a Magic Dictionary to handle word searches with one-character modifications. Implementing this solution involves building a dictionary first and efficiently searching for matches. The key approach leverages hash tables for fast lookups and string comparisons.

Problem Statement

The Magic Dictionary is a data structure that allows you to initialize it with a list of words. After the dictionary is built, you should be able to search for a word and check if it can match any word from the dictionary by modifying exactly one character. For example, searching for 'hello' could return a match if one character change would result in a word in the dictionary, like 'hhllo'.

To solve this, you must implement the MagicDictionary class that supports two functions: buildDict to initialize the dictionary and search to check for possible matches. Constraints are given regarding the dictionary and search word lengths, ensuring that the dictionary will be built once, followed by multiple search calls.

Examples

Example 1

Input: See original problem statement.

Output: See original problem statement.

Input ["MagicDictionary", "buildDict", "search", "search", "search", "search"] [[], [["hello", "leetcode"]], ["hello"], ["hhllo"], ["hell"], ["leetcoded"]] Output [null, null, false, true, false, false]

Explanation MagicDictionary magicDictionary = new MagicDictionary(); magicDictionary.buildDict(["hello", "leetcode"]); magicDictionary.search("hello"); // return False magicDictionary.search("hhllo"); // We can change the second 'h' to 'e' to match "hello" so we return True magicDictionary.search("hell"); // return False magicDictionary.search("leetcoded"); // return False

Constraints

  • 1 <= dictionary.length <= 100
  • 1 <= dictionary[i].length <= 100
  • dictionary[i] consists of only lower-case English letters.
  • All the strings in dictionary are distinct.
  • 1 <= searchWord.length <= 100
  • searchWord consists of only lower-case English letters.
  • buildDict will be called only once before search.
  • At most 100 calls will be made to search.

Solution Approach

Hash Table for Efficient Lookups

Use a hash table to store words, where the keys are the words and the values track their occurrence or positions. This allows quick lookups when searching for words and reduces the need for exhaustive search, leveraging the speed of hashing.

String Comparison with One Character Modification

For each search word, compare it with every word in the dictionary, checking if one character change can transform the search word into a dictionary word. A helper function can be used to track character differences during this comparison, ensuring only one mismatch occurs.

Efficient Search Handling

To efficiently search for a word, iterate through the dictionary and use a depth-first search (DFS) or a simple iteration to find exactly one character difference. This minimizes the search time for each word.

Complexity Analysis

Metric Value
Time Depends on the final approach
Space Depends on the final approach

The time complexity depends on the approach for comparison, typically O(n * m) where n is the number of words in the dictionary and m is the length of each word. The space complexity is O(n * m) due to storing the dictionary in a hash table.

What Interviewers Usually Probe

  • Look for an approach that efficiently handles multiple search calls after a single buildDict.
  • Ensure the candidate is considering both time and space complexities when implementing the solution.
  • Evaluate the candidate's ability to balance simplicity with performance, especially for string comparison and hash table usage.

Common Pitfalls or Variants

Common pitfalls

  • Overcomplicating the search logic by checking all character permutations instead of focusing on one-character changes.
  • Using inefficient data structures that result in slower lookups for large dictionaries.
  • Ignoring edge cases such as words that are too short or when no valid word can be matched after modification.

Follow-up variants

  • Allowing multiple character changes in the search (e.g., two or more changes).
  • Supporting wildcard searches where multiple characters may change.
  • Optimizing further for large-scale dictionaries with millions of entries.

FAQ

What is the main challenge in solving the "Implement Magic Dictionary" problem?

The main challenge is efficiently searching for a word with one character modification, ensuring both time and space complexity are minimized with appropriate data structures like hash tables.

How can hash tables improve the performance of the Magic Dictionary?

Hash tables allow for fast lookups, which are essential when checking if a word can be transformed by modifying one character. This significantly speeds up the search process compared to brute-force methods.

What is the time complexity for the "Implement Magic Dictionary" problem?

The time complexity is O(n * m), where n is the number of words in the dictionary and m is the length of each word, due to the need to compare each word in the dictionary with the search word.

Can this problem be solved without a hash table?

While it can be solved without a hash table, using one significantly reduces the time complexity for search operations. A hash table provides faster lookups, which is crucial when handling multiple search queries.

How does the "Implement Magic Dictionary" problem test string manipulation skills?

This problem tests the ability to efficiently compare strings and handle small modifications, such as changing one character, while maintaining optimal time complexity using appropriate data structures.

terminal

Solution

Solution 1: Trie + DFS

We can use a trie to store all the words in the dictionary. For each word we search, we use depth-first search. Specifically, we start from the root of the trie. For the current letter we are traversing, we first check whether there is a child node that is the same as it. If there is, we continue to traverse downwards. Otherwise, we need to check whether there are remaining modification times. If not, it means that it cannot be matched, so we return false. If there are remaining modification times, we can try to modify the current letter and continue to traverse downwards. If the child node corresponding to the modified letter exists, it means that it can be matched, otherwise it means that it cannot be matched, so we return false. If we traverse to the end of the word and the number of modifications is exactly 1, it means that it can be matched, so we return true.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class Trie:
    __slots__ = "children", "is_end"

    def __init__(self):
        self.children: List[Optional[Trie]] = [None] * 26
        self.is_end = False

    def insert(self, w: str) -> None:
        node = self
        for c in w:
            idx = ord(c) - ord("a")
            if node.children[idx] is None:
                node.children[idx] = Trie()
            node = node.children[idx]
        node.is_end = True

    def search(self, w: str) -> bool:
        def dfs(i: int, node: Optional[Trie], diff: int) -> bool:
            if i == len(w):
                return diff == 1 and node.is_end
            j = ord(w[i]) - ord("a")
            if node.children[j] and dfs(i + 1, node.children[j], diff):
                return True
            return diff == 0 and any(
                node.children[k] and dfs(i + 1, node.children[k], 1)
                for k in range(26)
                if k != j
            )

        return dfs(0, self, 0)


class MagicDictionary:
    def __init__(self):
        self.trie = Trie()

    def buildDict(self, dictionary: List[str]) -> None:
        for w in dictionary:
            self.trie.insert(w)

    def search(self, searchWord: str) -> bool:
        return self.trie.search(searchWord)


# Your MagicDictionary object will be instantiated and called as such:
# obj = MagicDictionary()
# obj.buildDict(dictionary)
# param_2 = obj.search(searchWord)
Implement Magic Dictionary Solution: Hash Table plus String | LeetCode #676 Medium