问题
实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作。
示例:
Trie trie = new Trie();
trie.insert("apple");
trie.search("apple"); // 返回 true
trie.search("app"); // 返回 false
trie.startsWith("app"); // 返回 true
trie.insert("app");
trie.search("app"); // 返回 true
说明:
你可以假设所有的输入都是由小写字母 a-z 构成的。
保证所有输入均为非空字符串。
基础知识
字典树,又称前缀树,是 N 叉树的特殊形式。通常来说,一个前缀树是用来存储字符串的。前缀树的每一个节点代表一个字符串(前缀)。每一个节点会有多个子节点,通往不同子节点的路径上有着不同的字符。子节点代表的字符串是由节点本身的原始字符串 ,以及通往该子节点路径上所有的字符组成的。
前缀树的一个重要的特性是,节点所有的后代都与该节点相关的字符串有着共同的前缀。这就是前缀树名称的由来。
我们再来看这个例子。例如,以节点 “b” 为根的子树中的节点表示的字符串,都具有共同的前缀 “b”。反之亦然,具有公共前缀 “b” 的字符串,全部位于以 “b” 为根的子树中,并且具有不同前缀的字符串来自不同的分支。
前缀树有着广泛的应用,例如自动补全,拼写检查等等
搜索字典项目的方法为:
- 从根结点开始一次搜索;
- 取得要查找关键词的第一个字母,并根据该字母选择对应的子树并转到该子树继续进行检索;
- 在相应的子树上,取得要查找关键词的第二个字母,并进一步选择对应的子树进行检索。
- 迭代过程……
- 在某个结点处,关键词的所有字母已被取出,则读取附在该结点上的信息,即完成查找。
其他操作类似。
trie的一般结构形式为:
struct TrieNode {
bool isEnd; //该结点是否是一个串的结束
TrieNode* next[NUM]; //键的值序列表
};
这里的键是指:一个字母就是一个键,如“word”中键长为4
例如:这里的NUM为26,字符串由26个小写字母组成,一个包含四个单词"word",“world”,“work”,"worded"的trie长这样:
其中的红点表示isEnd为true,简化表示后就是这样:
解题
public class Trie
{
/** Initialize your data structure here. */
class TrieNode
{
private boolean isEnd;
private TrieNode[] next;
public TrieNode()
{
isEnd = false;
next = new TrieNode[26]; //每个节点至多有26个小写字母
}
}
private static TrieNode root;
Trie()
{
root = new TrieNode();
}
/**
* Inserts a word into the trie.
* @param word 全部由小写字母组成的字符串
*
*/
public void insert(String word)
{
TrieNode cur = root;
for (int i = 0;i<word.length();i++)
{
int index = word.charAt(i) - 'a'; //计算next中的下标,确定字母
if (cur.next[index] == null)
{
cur.next[index] = new TrieNode(); //将word的每一个字母创建一个TrieNode,并插入正确的TrieNode数组中
}
cur = cur.next[index];
}
cur.isEnd = true;
}
/** Returns if the word is in the trie. */
public boolean search(String word)
{
TrieNode cur = root;
for (int i = 0;i<word.length();i++)
{
int index = word.charAt(i) - 'a';
if (cur.next[index] == null)
{
return false;
}
cur = cur.next[index];
}
return cur.isEnd;
}
/** Returns if there is any word in the trie that starts with the given prefix. */
public boolean startsWith(String prefix)
{
TrieNode cur = root;
for (int i = 0;i<prefix.length();i++)
{
int index = prefix.charAt(i) - 'a';
if (cur.next[index] == null) //如果未遍历完出现next中的值为空则返回false,遍历完说明prefix在前缀树中存在
{
return false;
}
cur = cur.next[index];
}
return true;
}
public static void main(String[] args) {
Trie trie = new Trie();
trie.insert("apple");
System.out.println(trie.search("apple")); // 返回 true
System.out.println(trie.search("app")); // 返回 false
System.out.println(trie.startsWith("app")); // 返回 true
trie.insert("app");
System.out.println(trie.search("app")); // 返回 true
}
}
复杂度分析
要对长度为n的字符串进行操作
insert: O(n)级时间复杂度
search: O(n)级时间复杂度
空间复杂度为 26n,但是在处理大量数据的时候,实际产生的空间要小于将每个字符串单个存储的大小。