单词替换

标签: 字典树 数组 哈希表 字符串

难度: Medium

在英语中,我们有一个叫做 词根(root) 的概念,可以词根后面添加其他一些词组成另一个较长的单词——我们称这个词为 继承词(successor)。例如,词根an,跟随着单词 other(其他),可以形成新的单词 another(另一个)。

现在,给定一个由许多词根组成的词典 dictionary 和一个用空格分隔单词形成的句子 sentence。你需要将句子中的所有继承词词根替换掉。如果继承词有许多可以形成它的词根,则用最短的词根替换它。

你需要输出替换之后的句子。

示例 1:

输入:dictionary = ["cat","bat","rat"], sentence = "the cattle was rattled by the battery"
输出:"the cat was rat by the bat"

示例 2:

输入:dictionary = ["a","b","c"], sentence = "aadsfasf absbs bbab cadsfafs"
输出:"a a b c"

提示:

  • 1 <= dictionary.length <= 1000
  • 1 <= dictionary[i].length <= 100
  • dictionary[i] 仅由小写字母组成。
  • 1 <= sentence.length <= 10^6
  • sentence 仅由小写字母和空格组成。
  • sentence 中单词的总量在范围 [1, 1000] 内。
  • sentence 中每个单词的长度在范围 [1, 1000] 内。
  • sentence 中单词之间由一个空格隔开。
  • sentence 没有前导或尾随空格。

Submission

运行时间: 86 ms

内存: 31.7 MB

class Solution:
    def replaceWords(self, dictionary: List[str], sentence: str) -> str:
        trie = Trie()
        for words in dictionary:
            trie.insert(words)
        words = sentence.split(' ')
        return' '.join(trie.Is_startWith(word) for word in words)
class Trie:
    def __init__(self):
        self.child=dict()
        self.count=0

    def insert(self,word):
        node = self
        for w in word:
            if w not in node.child:
                node.child[w] = Trie()
            node = node.child[w]
        node.count = 1

    def Is_startWith(self,word):
        node = self
        i = 0
        for w in word:
            if w not in node.child:
                break
            node = node.child[w]
            if node.count > 0 :
                word = word[:i+1]
            i+=1
        return word

Explain

该题解使用了字典树(Trie)来存储词根,并通过在字典树中查找单词的前缀来实现单词替换。具体思路如下: 1. 将词典中的所有词根插入到字典树中 2. 将句子按空格分割成单词列表 3. 对于每个单词,在字典树中查找其最短的词根前缀,并用该前缀替换原单词 4. 将替换后的单词列表拼接成最终的句子

时间复杂度: O((m+n)*w),其中 m 为词根数量,n 为句子中的单词数量,w 为单词的平均长度

空间复杂度: O(m*w + n),其中 m 为词根数量,w 为词根的平均长度,n 为句子中的单词数量

class Solution:
    def replaceWords(self, dictionary: List[str], sentence: str) -> str:
        trie = Trie()
        for words in dictionary:
            trie.insert(words)  # 将词根插入字典树
        words = sentence.split(' ')  # 将句子分割成单词列表
        return ' '.join(trie.Is_startWith(word) for word in words)  # 替换单词并拼接成句子

class Trie:
    def __init__(self):
        self.child = dict()  # 存储子节点
        self.count = 0  # 标记是否为词根结尾

    def insert(self, word):
        node = self
        for w in word:
            if w not in node.child:
                node.child[w] = Trie()  # 创建子节点
            node = node.child[w]  # 移动到子节点
        node.count = 1  # 标记词根结尾

    def Is_startWith(self, word):
        node = self
        i = 0
        for w in word:
            if w not in node.child:  # 如果当前字符不在字典树中,说明不是词根,直接返回原单词
                break
            node = node.child[w]  # 移动到子节点
            if node.count > 0:  # 如果当前节点是词根结尾,则用词根替换原单词
                word = word[:i+1]
            i += 1
        return word

Explore

在构建字典树时,如果词根重复插入,字典树的设计确保不会创建重复的节点。具体来说,当插入一个词根时,从字典树的根节点开始,对词根中的每一个字符进行遍历。如果该字符已经存在于当前节点的子节点中,则直接沿着该子节点继续遍历下一个字符;如果不存在,则创建一个新的子节点,并继续遍历。这样的设计确保了即使同一个词根被多次插入,也只会在第一次插入时创建必要的节点,后续的插入只会重复遍历这些节点,而不会创建新节点。

在查找单词的最短词根前缀时,根据题解的逻辑,优先选择最短的词根进行替换。这意味着,如果单词'cattle'既匹配'cat'又匹配'cattle',则会选择'cat'作为替换词根。这是因为一旦在字典树中遍历到一个标记为词根结尾的节点(即节点的`count`属性大于0),就会停止遍历并使用当前的词根前缀替换原单词。这种策略旨在使用尽可能短的词根来实现替换,以减少单词的长度,提高语句的简洁性。

在字典树的节点中使用`count`属性而非布尔类型来标记是否为词根结尾,可能是为了提供更多的灵活性和信息。虽然在当前的题解中,`count`属性似乎只用于区分是(1)或否(0)是一个词根的结尾,但使用整数类型的`count`可以允许未来的扩展,例如记录某个词根在词典中出现的次数或其他相关的统计信息。此外,这种方式也可以方便地进行修改以应对不同的需求,而不仅仅是标记存在与否。

在处理替换后的单词列表拼接成句子时,原题解中没有特别说明如何处理标点符号和特殊字符。理想的处理方式应该是在分割句子为单词列表之前,先识别并保留这些标点和特殊字符的位置信息。在进行单词替换后,再根据这些保留的位置信息将单词和标点符号按原来的顺序重新组合。这样可以确保句子的原始结构在替换过程中不受影响,保持语义和语法的正确性。如果题解中没有处理这一点,那么在实际应用中可能需要对该算法进行扩展,以支持对非字母字符的正确处理。