每日一刷经验分享:力扣648中等. 单词替换

175 阅读4分钟

736困难. Lisp 语法解析

题意

在英语中,我们有一个叫做 词根(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 没有前导或尾随空格。

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

AC代码

👀Java版本:

class Solution {
    public String replaceWords(List<String> dictionary, String sentence) {
        Dict root = new Dict();
        for(String s:dictionary){
            insert(root,s);
        }
        String[] ss = sentence.split(" ");
        StringBuilder sb = new StringBuilder();

        for(String s:ss){
            sb.append(find(root,s)+" ");
        }
        int len = sb.length();
        return sb.toString().substring(0,len-1);
    }
    public void insert(Dict root,String s){
        char[] chs = s.toCharArray();
        int len = chs.length;
        for(int i=0;i<len;i++){
            if(root.next[chs[i]-'a']==null){
                root.next[chs[i]-'a']=new Dict();
            }
            root = root.next[chs[i]-'a'];
        }
        root.flag=1;
    }
    public String find(Dict root,String s){
        char[] chs = s.toCharArray();
        int len = chs.length;
        int i=0;
        for( i=0;i<len;i++){
            if(root.next[chs[i]-'a']==null) break;
            if(root.next[chs[i]-'a'].flag==1){
                root = root.next[chs[i]-'a'];
                break;
            }
            root = root.next[chs[i]-'a'];
        }
        if(root.flag==1) return s.substring(0,i+1);
        else return s;
    }

}
public class Dict{
    public int flag;
    public Dict[] next=new Dict[26];
    public Dict(){
        for(int i=0;i<26;i++){
            next[i]=null;
            flag=0;
        }
    }
}

👀Python版本:

class Solution:
    def replaceWords(self, dictionary: List[str], sentence: str) -> str:
        trie = Trie()
        for d in dictionary:
            trie.insert(d)
        words = sentence.split(" ")
        return " ".join(trie.find_sp(word) for word in words)

class Trie:
    def __init__(self):
        self.root = {}
    
    def insert(self, word):
        node = self.root
        for w in word + "#":
            if w not in node:
                node[w] = {}
            node = node[w]
    
    def find(self, word):
        node = self.root
        for i in range(len(word)):
            if "#" in node:
                if self.find(word[i:]):
                    return True
            if word[i] in node:
                node = node[word[i]]
            else:
                return False
        return "#" in node
    
    def find_sp(self, word: str) -> str:
        node = self.root
        for i in range(len(word)):
            if "#" in node:
                return word[:i]
            if word[i] in node:
                node = node[word[i]]
            else:
                break
        return word

分析

根据题意可以看出有词根和单词,我们需要做的就是找出对应的单词的最小词根,所以本题就是求前缀树根,所以我们首先根据词根数组里的字符串建立对应的字典树,然后在根据要求词根的字符串找到最小的词根,因为词根结尾的节点的flag是等于1的,在我们字典树遍历的时候,只需要在第一次遍历到字典树中的节点为1的时候就可以返回当前的子串,就是最小的词根。

题解过程分析

  1. public class Dict{} 定义一个字典树的结构,用于之后的建立字典树和遍历字典树,字典树种含有flag表示是否有以这个点结尾的词根,next表示下一个字符的存在的情况下指向的节点。
  2. public void insert(Dict root,String s),把s中的字符串插入到root为根的字典树中,并把最后一个字符的节点的flag置为1,比爱是有以此节点为结尾的词根存在。
  3. public String find(Dict root,String s){}根据s查找在root为根的字典树种最小的词根是什么并返回查找道德词根,这个需要注意的就是可能字典树root种并不存在对应字符串s的词根,在不存在的情况下,需要返回整个字符串。
  4. public String replaceWords(List dictionary, String sentence)就是返回sentence中词语被替换为词根后字符串,这个需要注意的是分割空格为不同字符串的时候,

图解过程分析

  • 构建字典树 Root.jpg

  • 遍历字典树 Root (1).jpg

时间复杂度

时间复杂度:O(d + s)。其中d是dictionary的字符数,s是sentence的字符数。
空间复杂度:O(d + s)。

总结

往往这类题,对字符串前缀进行处理的我们可以考虑字典树的结构去处理,当然java中还有startWith(sub),但是这个时间消耗会相对较高,在解答本题的时候,也发现了一个点就是在分割字符串的时候使用\s+ 来匹配空格会耗费时间较多几乎占据了一般的时间,把这个改成' '时间的耗费里边减少一半,所以处理这个的时候也是需要注意的。

贴个startwith的暴力代码

public String replaceWords(List<String> dictionary, String sentence) {
    String[] s = sentence.split(" ");
    for (String root : dictionary) {
        for (int i = 0; i < s.length; i++) {
            if (s[i].startsWith(root)) {
                s[i] = root;
            }
        }
    }
    return String.join(",", s);
}

上边的这个代码不那么复杂,直接使用暴力的思想把所有的根存储起来,然后遍历每一个词语,每个词语查找是否是startwith(词根),然后替换即可,就算是有两个词根如abc和ab,在第一次替换为abc后还会再替换为ab,所以这个处理之后存储的还是最小值。最后join(ss,","),把数组连接起来即可。

image.png 我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿