持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第16天,点击查看活动详情
1.描述
给出一个字符串数组 words 组成的一本英语词典。返回 words 中最长的一个单词,该单词是由 words 词典中其他单词逐步添加一个字母组成。
若其中有多个可行的答案,则返回答案中字典序最小的单词。若无答案,则返回空字符串。
示例 1:
输入:words = ["w","wo","wor","worl", "world"]
输出:"world"
解释: 单词"world"可由"w", "wo", "wor", 和 "worl"逐步添加一个字母组成。
示例 2:
输入:words = ["a", "banana", "app", "appl", "ap", "apply", "apple"]
输出:"apple"
解释:"apply" 和 "apple" 都能由词典中的单词组成。但是 "apple" 的字典序小于 "apply"
提示:
- 1 <= words.length <= 1000
- 1 <= words[i].length <= 30
- 所有输入的字符串 words[i] 都只包含小写字母。
2.分析
trie树即前缀树,可以方便检索和前缀相关的问题,题目确定了只有小写字母,所以每个树节点直接开26个子节点,不用动态开节点。
逻辑不是很难,有一些小细节,比如满足答案的刚好只有一个字符长度,我引入了父节点来排除这个情况,再比如遍历某个单词的字符时,如果这个该单词的字符对应的节点在trie中end状态为false,则说明该单词不可能由给定列表单词累增一个字符得到,直接break就好
3.AC代码
public class Solution {
Tire root = new Tire('#');
public String longestWord(String[] words) {
//根据给定的单词构建字典树
for(String w: words){
add(w);
}
String ans = "";
int max = Integer.MIN_VALUE;
//遍历每个单词在字典树中的状态
for(String w: words){
Tire p = root;
for(char c: w.toCharArray()){
int index = c - 'a';
p = p.child[index];
if(!p.end) break; //如果当前遍历的字典树节点不是一个单词的结尾,说明目前遍历的字典树的节点组成的单词不是给定单词列表中单词的前缀
}
//p.end限制了上一个循环只能是完全遍历完某单词才退出的
if((p.prev.end || p.prev.data == '#') && p.end){
if(w.length() > max){
max = w.length();
ans = w;
}else if(w.length() == max){
//满足条件的相同长度单词取字典序最小,目前未找到java比较快捷的比较方式-.-
for(int i = 0; i < w.length(); i++){
if(w.charAt(i) < ans.charAt(i)){
ans = w;
break;
}else if(w.charAt(i) > ans.charAt(i)){
break;
}
}
}
}
}
return ans;
}
void add(String s){
Tire pre = null;
Tire p = root;
for(char c: s.toCharArray()){
int index = c - 'a';
if(p.child[index] == null){
p.child[index] = new Tire(c);
}
pre = p;
p = p.child[index];
p.prev = pre;
}
p.end = true;
}
}
class Tire{
public Tire prev; //父节点指针
public Tire[] child;
public char data; //当前节点存放的字符(根节点标记为'#')
public Tire(char c){
data = c;
child = new Tire[26]; //创建26子节点对应了只可能是小写字母,26个
}
public boolean end; //标记当前是否是一个单词的结尾
}