LeetCode LCR 063. 单词替换
题目描述


题意分析
用词典中的词根替换句子里的单词:词根必须是该单词的前缀,若有多个匹配词根,就选择长度最短的一个;没有匹配词根时保留原单词。
题目保证单词之间只有一个空格,且句子没有首尾空格,因此可以按单空格拆分、分别处理,再按同样的分隔方式连接。词根最长为 $100$,句中单词最长为 $1000$,两种长度上限不能混用。
解法:从短到长枚举词根前缀
核心思路
[!blue]
一个能替换当前单词的词根,必然等于它的某个前缀。与其为每个单词遍历整个词典,可以先把词根放入集合
s,再枚举这个单词可能匹配的前缀,直接判断它是否属于词典。设词典中最长词根长度为
maxRoot。长度超过maxRoot的前缀不可能是词根,长度超过单词自身也没有意义,所以只需检查长度 $1$ 到min(word.length, maxRoot)。前缀按长度递增枚举。第一次命中时,所有更短的前缀都已经确认不是词根,因此当前命中的就是最短词根;立即替换并退出,后面即使还有更长的匹配也不应采用。若全部候选都失败,这个单词不存在匹配词根,保持原值即可。
每个单词的替换只由它自身与固定词典决定,不会影响其他单词的匹配。因此可以依次更新
words中的对应位置,最后用单空格连接。词根等于整个单词时也属于合法前缀,替换后的内容只是与原词相同。
解题步骤
- 建立词根集合
s,扫描词典得到最长词根长度maxRoot。- 按单空格拆分句子,逐个读取原单词
word。- 令前缀长度从 $1$ 增长到单词长度与
maxRoot的较小值,查询此前缀是否在集合中。- 首次命中就写回该前缀并结束当前单词的循环;没有命中则保留原词。
- 用单空格连接结果并返回。
代码实现
class Solution {
public String replaceWords(List<String> dictionary, String sentence) {
Set<String> s = new HashSet<>(dictionary);
int maxRoot = 0;
for (String root : dictionary) {
maxRoot = Math.max(maxRoot, root.length());
}
String[] words = sentence.split(" ");
for (int i = 0; i < words.length; ++i) {
String word = words[i];
for (int j = 1; j <= word.length() && j <= maxRoot; ++j) {
String t = word.substring(0, j);
if (s.contains(t)) {
words[i] = t;
break;
}
}
}
return String.join(" ", words);
}
}
import (
"strings"
)
func replaceWords(dictionary []string, sentence string) string {
s := map[string]bool{}
maxRoot := 0
for _, v := range dictionary {
s[v] = true
maxRoot = max(maxRoot, len(v))
}
words := strings.Split(sentence, " ")
for i, word := range words {
for j := 1; j <= len(word) && j <= maxRoot; j++ {
t := word[:j]
if s[t] {
words[i] = t
break
}
}
}
return strings.Join(words, " ")
}
复杂度分析
- 时间复杂度:设词根总字符数为
D、句子长度为S、单词数为N,第i个单词长度为L_i,最多检查的前缀长度为 $B_i=\min(L_i,R)$,其中R是最长词根长度。期望时间为 $O(D+S+\sum B_i^2)$,也可上界为 $O(D+S+NR^2)$。长度为j的前缀需要按其字符计算哈希,Java 还会复制子串,不能把每次前缀查询都视为不计字符长度的常数操作。- 空间复杂度:$O(D+S)$ 的上界,用于词根集合、分词结果和返回字符串。
关键点总结
[!green]
- 所有合法词根都在单词前缀中,集合查询只需判断成员是否存在。
- 递增长度的第一次命中就是最短匹配,必须立即停止。
maxRoot限制候选长度,避免为长单词生成不可能命中的更长前缀。
易错点总结
[!yellow]
- 从长前缀开始或命中后继续覆盖,会违背最短词根要求。
- 查找任意子串不能代替前缀匹配,词根必须从单词首字符开始。
- 候选长度应允许等于整个单词长度,词根与单词相同也合法。
- 未命中的单词要原样保留,不能丢弃或替换为空串。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 208. 实现 Trie (前缀树) | 中等 | 复用Trie前缀查找,本题遇到第一个词根终点立即停止,以保留最短词根。 |
| 720. 词典中最长的单词 | 中等 | 同样利用前缀终点标记,原题要求构建单词的每一级前缀都存在,本题只需最短匹配词根。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!