题目来源:https://leetcode-cn.com/problems/design-add-and-search-words-data-structure/
大致题意:
实现一个词典类:
- 有构造函数可以初始化
- 有 addWord(String word) 方法,可以添加一个单词到当前对象
- 有 search(String word) 方法,搜索当前对象中是否有该单词,若有返回 true,否则返回 false。word 中可能包含一些 ‘.’,每个 ‘.’ 可以表达任意字符
思路
使用前缀树进行单词搜索是一个好方法
前缀树
对于本题,基本的前缀树数据结构就可以解决 addWord 方法
而对于 search 方法,需要考虑到出现 ‘.’ 的情况
- 搜索过程中,若当前字符为正常的小写字母,那么按照前缀树的搜索方法,即查找当前节点是否存在当前字符的子节点,若有则递归查询下一位;若当前字符为 ‘.’ ,那么就对当前节点的所有非空子节点递归查询下一位
代码:
public class WordDictionary {
private Trie root;
public WordDictionary() {
root = new Trie();
}
public void addWord(String word) {
root.insert(word);
}
public boolean search(String word) {
return root.search(word);
// return dfs(word, 0, root);
}
// 题解上的方法,dfs
private boolean dfs(String word, int index, Trie node) {
// 已经遍历到最后一个字符对应节点,若当前节点为单词末尾,则表示找到 word
if (index == word.length()) {
return node.getIsEnd();
}
char ch = word.charAt(index);
// 如果当前字符为 .
if (!Character.isLetter(ch)) {
// dfs 所有非空子节点
for (int i = 0; i < 26; i++) {
Trie child = node.getChildren()[i];
if (child != null && dfs(word, index + 1, child)) {
return true;
}
}
} else {
// dfs 当前字符对应的子节点
// 如果为空,则会执行末尾语句,返回 false
int idx = ch - 'a';
Trie child = node.getChildren()[idx];
if (child != null && dfs(word, index + 1, child)) {
return true;
}
}
return false;
}
}
// 创建一个前缀树的类
class Trie{
private Trie[] children; // 存储子节点
private boolean isEnd; // 标记当前节点是否为某个单词尾部,即查找到当前节点即为查到对应单词
public Trie() {
children = new Trie[26];
isEnd = false;
}
// 插入新词
public void insert(String word) {
Trie node = this;
for (int i = 0; i < word.length(); i++) {
int idx = word.charAt(i) - 'a';
// 若当前字符对应的子节点为空,则新建该子节点
if (node.children[idx] == null) {
node.children[idx] = new Trie();
}
node = node.children[idx];
}
node.isEnd = true;
}
public boolean search(String word) {
Trie node = this;
for (int i = 0; i < word.length(); i++) {
// 当前字符为正常字符
if (word.charAt(i) != '.') {
int idx = word.charAt(i) - 'a';
// 对应子节点为空,直接返回
if (node.children[idx] == null) {
return false;
}
// 递归到子节点
node = node.children[idx];
} else { // 当前字符为 .
// 若 . 为最后一个字符
if (i == word.length() - 1) {
// 只要子节点有一个不为空且其为单词末尾,则代表找到当前单词
for (int j = 0; j < 26 ; j++) {
if (node.children[j] != null && node.children[j].isEnd) {
return true;
}
}
}
// 在所有非空子节点中递归查询剩下的字符串
for (int j = 0; j < 26; j++) {
if (node.children[j] != null && node.children[j].search(word.substring(i + 1, word.length()))) {
System.out.println(word.substring(i + 1, word.length()));
return true;
}
}
return false;
}
}
return node.isEnd;
}
public boolean getIsEnd() {
return this.isEnd;
}
public Trie[] getChildren() {
return this.children;
}
}