题目来源:https://leetcode-cn.com/problems/concatenated-words/
大致题意:
给定一个字符串数组,判断其中的长字符串能否由短字符串连接组成,返回能被短串组成的长串
思路
这波是前缀树(字典树)的搜索,老朋友换新衣服
既然要用短串组成长串,那么就需要先把短串插入字典树中,然后遇到长串就搜索
排序 + 前缀树 + DFS
- 构建前缀树类
- 将给定字符串数组排序,然后遍历字符串数组,遇到空串直接跳过
- 对于当前遍历的字符串,先在前缀树中搜索它是否可以由存入的字符串组成,若可以加入答案集合,否则加入前缀树
在前缀树中搜索当前字符串能否被存入的字符串组成的方法可以概括为:
- 从索引 0 开始遍历字符串
- 若当前位置字符在前缀树中对应节点为空,直接返回 false
- 若当前位置不为空,且当前位置是一个字符串结尾,于是标记位置,从下一个位置开始重新在前缀树中搜索,也就是找到了拼接的字符串,再在剩余部分找下一个字符串。若仍找到,那么重复搜索,直至未找到对应串返回 false,或者字符串遍历结束返回 true
- 若当前位置不为空,但当前位置不是结尾,顺序遍历下一个
代码:
public class FindAllConcatenatedWordsInADict {
Trie trie = new Trie();
public List<String> findAllConcatenatedWordsInADict(String[] words) {
List<String> ans = new ArrayList<>();
// 排序
Arrays.sort(words, (a, b) -> a.length() - b.length());
for (String word : words) {
// 跳过空串
if (word.length() == 0) {
continue;
}
// 在前缀树遍历字符串,若有加入答案集合
if (dfs(word, 0)) {
ans.add(word);
} else { // 没有则插入前缀树
insert(word);
}
}
return ans;
}
// 前缀树搜索
public boolean dfs(String word, int start) {
int n = word.length();
// 若开始位置即为字符串长度,表示搜索完毕,word 可以由前缀树的字符串拼接组成
if (n == start) {
return true;
}
Trie node = trie;
for (int i = start; i < n; i++) {
int idx = word.charAt(i) - 'a';
// 若对应子节点不存在,直接返回 false
if (node.children[idx] == null) {
return false;
}
node = node.children[idx];
// 若子节点为前缀树中存的结尾节点
if (node.isEnd) {
// 那么从下一位置开始,重新在前缀树中搜索
// 剩余部分也搜索到,返回 true
if (dfs(word, i + 1))
return true;
}
}
// 之前未返回 true,即代表当前串不可以由前缀树中的串拼接组成
return false;
}
// 前缀树插入
public void insert(String word) {
Trie node = trie;
int n = word.length();
for (int i = 0; i < n; i++) {
int idx = word.charAt(i) - 'a';
if (node.children[idx] == null) {
node.children[idx] = new Trie();
}
node = node.children[idx];
}
node.isEnd = true;
}
// 前缀树类
class Trie {
Trie[] children;
boolean isEnd;
public Trie() {
children = new Trie[26];
isEnd = false;
}
}
}