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的时候就可以返回当前的子串,就是最小的词根。
题解过程分析
- public class Dict{} 定义一个字典树的结构,用于之后的建立字典树和遍历字典树,字典树种含有flag表示是否有以这个点结尾的词根,next表示下一个字符的存在的情况下指向的节点。
- public void insert(Dict root,String s),把s中的字符串插入到root为根的字典树中,并把最后一个字符的节点的flag置为1,比爱是有以此节点为结尾的词根存在。
- public String find(Dict root,String s){}根据s查找在root为根的字典树种最小的词根是什么并返回查找道德词根,这个需要注意的就是可能字典树root种并不存在对应字符串s的词根,在不存在的情况下,需要返回整个字符串。
- public String replaceWords(List dictionary, String sentence)就是返回sentence中词语被替换为词根后字符串,这个需要注意的是分割空格为不同字符串的时候,
图解过程分析
-
构建字典树
-
遍历字典树
时间复杂度
时间复杂度: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,","),把数组连接起来即可。
我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿