要求
Trie(发音类似 “try”)或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。
请你实现 Trie 类:
Trie() 初始化前缀树对象。
void insert(String word) 向前缀树中插入字符串 word 。
boolean search(String word) 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false 。
boolean startsWith(String prefix) 如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。
思路
方法一:二维数组
使用二维数组 trie[] 来存储我们所有的单词字符。
使用 index 来自增记录我们到底用了多少个格子(相当于给被用到格子进行编号)。
使用 count[] 数组记录某个格子被「被标记为结尾的次数」(当 idx 编号的格子被标记了 n 次,则有 cnt[idx]=n)。
class Trie {
int N = 100009; // 直接设置为十万级
int[][] trie;
int[] count;
int index;
public Trie() {
trie = new int[N][26];
count = new int[N];
index = 0;
}
public void insert(String s) {
int p = 0;
for (int i = 0; i < s.length(); i++) {
int u = s.charAt(i) - 'a';
if (trie[p][u] == 0) trie[p][u] = ++index;
p = trie[p][u];
}
count[p]++;
}
public boolean search(String s) {
int p = 0;
for (int i = 0; i < s.length(); i++) {
int u = s.charAt(i) - 'a';
if (trie[p][u] == 0) return false;
p = trie[p][u];
}
return count[p] != 0;
}
public boolean startsWith(String s) {
int p = 0;
for (int i = 0; i < s.length(); i++) {
int u = s.charAt(i) - 'a';
if (trie[p][u] == 0) return false;
p = trie[p][u];
}
return true;
}
}
- 时间复杂度:Trie 树的每次调用时间复杂度取决于入参字符串的长度。复杂度为 O(Len)。
- 空间复杂度:二维数组的高度为 n,字符集大小为 k。复杂度为 O(nk)。
方法二:字典树Tire
Trie,又称前缀树或字典树,是一棵有根树,其每个节点包含以下字段:
- 指向子节点的指针数组children。对于本题而言,数组长度为 26,即小写英文字母的数量。此时children[0] 对应小写字母 a,children[1] 对应小写字母 b,children[25] 对应小写字母 z。
- 布尔字段 isEnd,表示该节点是否为字符串的结尾。
插入字符串
我们从字典树的根开始,插入字符串。对于当前字符对应的子节点,有两种情况:
- 子节点存在。沿着指针移动到子节点,继续处理下一个字符。
- 子节点不存在。创建一个新的子节点,记录在children 数组的对应位置上,然后沿着指针移动到子节点,继续搜索下一个字符。
重复以上步骤,直到处理字符串的最后一个字符,然后将当前节点标记为字符串的结尾。
查找前缀
我们从字典树的根开始,查找前缀。对于当前字符对应的子节点,有两种情况:
- 子节点存在。沿着指针移动到子节点,继续搜索下一个字符。
- 子节点不存在。说明字典树中不包含该前缀,返回空指针。
重复以上步骤,直到返回空指针或搜索完前缀的最后一个字符。
若搜索到了前缀的末尾,就说明字典树中存在该前缀。此外,若前缀末尾对应节点的 isEnd 为真,则说明字典树中存在该字符串。
class Trie {
private Trie[] children;
private boolean isEnd;
public Trie() {
//为了储存26个字符a-z,创建出来的空间
children = new Trie[26];
//是否为字符串的结尾
isEnd = false;
}
public void insert(String word) {
Trie node = this;
for (int i = 0; i < word.length(); i++) {
char ch = word.charAt(i);
//a的小标为0
int index = ch - 'a';
//子节点不存在。创建一个新的子节点,
if (node.children[index] == null){
node.children[index] = new Trie();
}
//记录在children 数组的对应位置上
node = node.children[index];
}
node.isEnd = true;
}
public boolean search(String word) {
Trie node = searchPrefix(word);
return node != null && node.isEnd;
}
public boolean startsWith(String prefix) {
return searchPrefix(prefix) != null;
}
//提炼成一个公共的查询方法
private Trie searchPrefix(String prefix) {
Trie node = this;
for (int i = 0; i < prefix.length(); i++) {
char ch = prefix.charAt(i);
//找出当前节点的下标,进行查询
int index = ch - 'a';
if (node.children[index] == null) {
return null;
}
//有的话就继续
node = node.children[index];
}
return node;
}
}