LeetCode 720. 词典中最长的单词
题目描述

题意分析
从字典中找一个单词,它能够从单字符单词开始,每次只在末尾添加一个字符,并且每一步得到的单词都在字典中。候选越长越好,长度相同时取字典序最小者;没有合法候选就返回空串。
对长度为
L的候选,构造过程中必然依次经过它长度为1..L-1的前缀,因此可以把逐步构造转成前缀是否存在的检查。
解法:哈希集合 + 前缀检查
核心思路
[!blue]
先把全部字典单词加入哈希集合。对于候选
w,依次检查w长度为1到len(w)-1的所有非空真前缀是否在集合中;候选本身来自输入,已经保证存在。这个条件既必要也充分。若能逐字母构造,每次长度只增加一,途中必须经过每一级前缀,所以它们都要在字典里;反过来,如果每一级前缀都存在,就能从单字符单词开始,每次在末尾补一个字符,逐步得到候选。任意一个前缀缺失,都可以立即放弃当前候选。
单字符单词没有非空真前缀,本身就能作为构造起点,因此检查循环为空时应视为合法。把集合预先建好也意味着查询与输入顺序无关,不必要求短词先出现。
对每个合法候选,先比较长度,再处理字典序:更长就更新答案;长度相同且字典序更小也更新。答案初始化为空串,若始终没有通过全部前缀检查的单词,就保持为空。
解题步骤
- 遍历输入,把所有完整单词加入哈希集合。
- 对每个候选,从长度
1开始检查所有非空真前缀,发现缺失就结束该候选的检查。- 若所有前缀都存在,按“长度更大,或同长但字典序更小”的条件更新答案。
- 全部候选处理完后返回答案。单字符候选直接合法,无合法候选时返回初始空串。
代码实现
class Solution {
public String longestWord(String[] words) {
Set<String> set = new HashSet<>();
for (String w : words) {
set.add(w);
}
String answer = "";
for (String w : words) {
if (!ok720(w, set)) {
continue;
}
// 先取最长,长度相同时取字典序更小的候选。
if (w.length() > answer.length()
|| (w.length() == answer.length() && w.compareTo(answer) < 0)) {
answer = w;
}
}
return answer;
}
private boolean ok720(String w, Set<String> set) {
// 所有非空真前缀都必须在字典中,不能跳过中间长度。
for (int i = 1; i < w.length(); i++) {
if (!set.contains(w.substring(0, i))) {
return false;
}
}
return true;
}
}
func longestWord(words []string) string {
set := make(map[string]struct{}, len(words))
for _, w := range words {
set[w] = struct{}{}
}
answer := ""
for _, w := range words {
if !ok720(w, set) {
continue
}
// 先取最长,长度相同时取字典序更小的候选。
if len(w) > len(answer) || (len(w) == len(answer) && w < answer) {
answer = w
}
}
return answer
}
func ok720(w string, set map[string]struct{}) bool {
// 所有非空真前缀都必须在字典中,不能跳过中间长度。
for i := 1; i < len(w); i++ {
if _, ok := set[w[:i]]; !ok {
return false
}
}
return true
}
复杂度分析
设单词数为
W,第i个单词长度为L_i,最长单词长度为L。
- 时间复杂度:期望 $O(\sum L_i^2)$。一个单词的各前缀长度之和是平方量级;Java 构造前缀和计算哈希都需要扫描字符,Go 虽可共享前缀内容,哈希仍需按前缀长度计算。
- 空间复杂度:不计输入字符串本身,集合保存 $O(W)$ 个引用或字符串描述。Java 当前前缀还需要最长 $O(L)$ 的临时空间,总辅助空间为 $O(W+L)$;Go 的前缀切片共享原字符串内容,辅助空间为 $O(W)$。
关键点总结
[!green]
- 每次只能在末尾添加一个字符,所以合法构造等价于所有中间长度的前缀都存在。
- 字典集合只表示完整单词存在,不保证它本身可逐步构造,因此仍需检查全部真前缀。
- 单字符单词是合法起点,不需要空串也出现在字典中。
- 长度是第一优先级,字典序只用于同长度候选的平局。
易错点总结
[!yellow]
- 只检查删除最后一个字符后的前缀,会漏掉更短中间前缀的缺失。
- 一边建立集合一边按输入顺序检查,会让尚未读到的短词被误认为不存在。
- 要求空串也在字典中,会错误排除所有单字符起点。
- 同长时直接保留最后遇到的候选,答案会依赖输入顺序,而非字典序。
- 将每次前缀查询简单当成常数成本,会忽略字符串构造和哈希涉及的字符扫描。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 208. 实现 Trie (前缀树) | 中等 | Trie的终点标记用于确认每一级前缀都是完整单词,本题不仅要求路径存在。 |
| 648. 单词替换 | 中等 | 同样沿Trie查词终点,原题取最短词根,本题寻找每级前缀都有效的最长单词。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!