问题描述:给定一个字符串数组,判断这个数组中是否包含某一个字符串word
,或判断这个数组是否包含前缀prefix
。
暴力的实现方法是将给定的字符串word
或prefix
与字符串数组一一比对,查找到了则返回true
,否则返回false
。这种情况下的时间复杂度为
O
(
∣
S
∣
n
)
O(\lvert S \rvert n)
O(∣S∣n),空间复杂度
O
(
∣
T
∣
)
O(\lvert T \rvert)
O(∣T∣)。其中
∣
S
∣
\lvert S \rvert
∣S∣代表给定字符串长度,
n
n
n代表数组长度,
∣
T
∣
\lvert T \rvert
∣T∣是字符串数组出现字符的总长度。
前缀树实现方式:时间复杂度为
O
(
∣
S
∣
)
O(\lvert S \rvert)
O(∣S∣)。空间复杂度
O
(
∣
T
∣
∣
Σ
∣
)
O(\lvert T \rvert \lvert \Sigma \rvert)
O(∣T∣∣Σ∣)。其中
∣
Σ
∣
\lvert \Sigma \rvert
∣Σ∣是字符集长度。
实现 Trie (前缀树)
对于Trie
树中的任意一个节点都包含一个长度为
∣
Σ
∣
\lvert \Sigma \rvert
∣Σ∣的节点数组,和一个isEnd
代表当前节点是否是某个单词的末尾的下一个节点。Trie
树中的每个节点不一定只代表一个字符。Trie
树中的每个节点都只有唯一一个父节点(根节点无父节点)
以下给出了Trie树构建的示例,一开始只有node1
节点并且node1
的所有位置都为null
- 插入一个单词
app
:
①新建node2
并将node1
的a
位置指向node2
②新建node3
并将node2
的p
位置指向node3
③新建node4
并将node3
的p
位置指向node4
④node4
的isEnd
为设为true
。 - 插入一个单词
apa
:
①新建node5
并将node3
的a
位置指向node5
②node5
的isEnd
设为true
。 - 插入一个单词
appa
:
①新建node6
并将node4
的a
位置指向node6
②node6
的isEnd
设为true
。 - 查找
apa
:
①node1
的a
位置指向node2
②node2
的p
位置指向node3
③node3
的a
位置指向node5
④node5
的isEnd
为true
,代表存在这样的单词。
(下面图片展示了插入app
和apa
后的字典树)
class Trie {
private Trie[] tree;
private boolean isEnd;
public Trie() {
tree = new Trie[26];
isEnd = false;
}
public void insert(String word) {
char[] chs = word.toCharArray();
Trie pr = this;
for (int i = 0; i < chs.length; i++) {
int ch = chs[i] - 'a';
if (pr.tree[ch] == null) {
pr.tree[ch] = new Trie();
}
pr = pr.tree[ch];
}
pr.isEnd = true;
}
public boolean search(String word) {
Trie pr = findTrie(word);
return pr != null && pr.isEnd;
}
public boolean startsWith(String prefix) {
return findTrie(prefix) != null;
}
//查找word的下一个节点,如果word='ap',那么上面的例子中就找到node3
public Trie findTrie(String word) {
char[] chs = word.toCharArray();
Trie pr = this;
for (int i = 0; i < chs.length; i++) {
int ch = chs[i] - 'a';
if (pr.tree[ch] == null) return null;
pr = pr.tree[ch];
}
return pr;
}
}