实现一个魔法字典

标签: 深度优先搜索 设计 字典树 哈希表 字符串

难度: Medium

设计一个使用单词列表进行初始化的数据结构,单词列表中的单词 互不相同 。 如果给出一个单词,请判定能否只将这个单词中一个字母换成另一个字母,使得所形成的新单词存在于你构建的字典中。

实现 MagicDictionary 类:

  • MagicDictionary() 初始化对象
  • void buildDict(String[] dictionary) 使用字符串数组 dictionary 设定该数据结构,dictionary 中的字符串互不相同
  • bool search(String searchWord) 给定一个字符串 searchWord ,判定能否只将字符串中 一个 字母换成另一个字母,使得所形成的新字符串能够与字典中的任一字符串匹配。如果可以,返回 true ;否则,返回 false

 

示例:

输入
["MagicDictionary", "buildDict", "search", "search", "search", "search"]
[[], [["hello", "leetcode"]], ["hello"], ["hhllo"], ["hell"], ["leetcoded"]]
输出
[null, null, false, true, false, false]

解释
MagicDictionary magicDictionary = new MagicDictionary();
magicDictionary.buildDict(["hello", "leetcode"]);
magicDictionary.search("hello"); // 返回 False
magicDictionary.search("hhllo"); // 将第二个 'h' 替换为 'e' 可以匹配 "hello" ,所以返回 True
magicDictionary.search("hell"); // 返回 False
magicDictionary.search("leetcoded"); // 返回 False

 

提示:

  • 1 <= dictionary.length <= 100
  • 1 <= dictionary[i].length <= 100
  • dictionary[i] 仅由小写英文字母组成
  • dictionary 中的所有字符串 互不相同
  • 1 <= searchWord.length <= 100
  • searchWord 仅由小写英文字母组成
  • buildDict 仅在 search 之前调用一次
  • 最多调用 100search

Submission

运行时间: 39 ms

内存: 16.3 MB

class MagicDictionary:
    def __init__(self):
        self.dict_map = {}

    def buildDict(self, dictionary: List[str]) -> None:
        for word in dictionary:
            length = len(word)
            if length not in self.dict_map:
                self.dict_map[length] = set()
            self.dict_map[length].add(word)

    def search(self, searchWord: str) -> bool:
        length = len(searchWord)
        if length not in self.dict_map:
            return False

        for word in self.dict_map[length]:
            diff_count = 0
            for i in range(length):
                if word[i] != searchWord[i]:
                    diff_count += 1
                    if diff_count > 1:
                        break
            if diff_count == 1:
                return True

        return False

Explain

这个题解首先在构建字典阶段(buildDict),将所有输入的单词按照长度分类存储到一个字典中,其中键是单词的长度,值是具有该长度的单词集合。这样做的目的是为了在搜索阶段能够快速定位到与待搜索单词长度相同的单词集合,从而降低搜索范围。在搜索阶段(search),如果待搜索单词的长度在构建的字典中找不到对应的键,则直接返回False。否则,遍历与搜索词长度相同的单词集合,通过逐字符比对的方式计算两个单词之间的不同字符数。如果恰好有一个字符不同,则返回True,表示可以通过改变一个字符使得单词存在于字典中。如果遍历完都没有找到,返回False。

时间复杂度: O(N * L)

空间复杂度: O(N * L)

class MagicDictionary:
    def __init__(self):
        # 初始化存储结构为字典,键为单词长度,值为该长度的单词集合
        self.dict_map = {}

    def buildDict(self, dictionary: List[str]) -> None:
        # 构建字典,将单词按长度分类存储
        for word in dictionary:
            length = len(word)
            if length not in self.dict_map:
                self.dict_map[length] = set()
            self.dict_map[length].add(word)

    def search(self, searchWord: str) -> bool:
        # 搜索指定单词是否可以通过改变一个字符匹配字典中的单词
        length = len(searchWord)
        if length not in self.dict_map:
            return False

        for word in self.dict_map[length]:
            diff_count = 0
            for i in range(length):
                if word[i] != searchWord[i]:
                    diff_count += 1
                    if diff_count > 1:
                        break
            if diff_count == 1:
                return True

        return False

Explore

选择按单词长度来组织字典是基于搜索效率的考虑。由于我们的目标是找到只有一个字符不同的单词,如果单词长度不同,则它们之间的字符数就已经不匹配,从而没有必要进行比较。而按字母顺序或哈希值组织字典可能会导致需要比较长度不同的单词,这不仅增加了比较次数,还可能会导致比较无效。因此,按长度分类可以直接缩小搜寻范围,提高搜索效率。

当前实现中主要使用了逐字符比对的方法,这种方法虽然直接但效率不是最优。可以考虑使用更高效的算法,如构建一个更复杂的数据结构(例如使用字典树Trie结合哈希表),预先计算并存储每个单词去除任一字符后可能的所有字符串形态,然后在搜索时,生成待搜索词的所有可能形态并快速查找这些形态是否存在。这种方法可以大大减少在搜索过程中的比较次数,提高效率。

使用set来存储同长度的单词主要是为了避免重复。如果字典中多次添加同一个单词,使用set可以自动处理重复的问题,保证每个长度的单词集合中的元素都是唯一的。此外,set的另一个优点是查找效率较高(平均情况下为常数时间复杂度),这对于在搜索阶段快速判断某个单词是否存在很有帮助。

在当前实现中,对于每个单词我们都进行了逐字符的比对,这在单词数量多或字符差异多的情况下效率较低。优化方法可以包括使用更高级的字符串比较算法,如位运算或哈希签名方法来快速排除那些差异字符多于一个的单词。另一个方法是,在构建字典时就预计算每个单词去掉每个字符后的模式,并将这些模式存储在一个高效的查找结构中,如哈希表,这样在搜索时可以直接查找模式匹配,从而避免逐个字符的比对。