题目描述(中等难度)
实现 Trie
数,即前缀树。trie
的发明者 Edward Fredkin 把它读作 /ˈtriː/ "tree"
。但是,其他作者把它读作/traɪ/"try"
。
解法一
算作一个高级的数据结构,实现方式可以通过 26
叉树。每个节点存一个字母,根节点不存字母。
"app" "as" "cat" "yes" "year" "you" root / | \ a c y / \ | / \ p s a e o / | / \ \ p t s a u | r 上图中省略了 null 节点,对于第一层画完整了其实是下边的样子, 图示中用 0 代表 null root / | | | | | | | | | | | | | | | | | | | | | | | | \ a 0 c 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 y 0 其它层同理
代码的话,我们定义一个节点,每个节点包含一个节点数组,代表 26
个孩子。此外,还需要一个 flag
变量来标记当前节点是否是某个单词的结束。
class TrieNode { TrieNode[] children; boolean flag; public TrieNode() { children = new TrieNode[26]; flag = false; //节点初始化为 null for (int i = 0; i < 26; i++) { children[i] = null; } } }
然后只需要实现题目中所需要的三个函数即可。其中 children[0]
存 a
, children[1]
存 b
, children[2]
存 c
... 依次类推。所以存的时候我们用当前字符减去 a
,从而得到相应的 children
下标。
class Trie { class TrieNode { TrieNode[] children; boolean flag; public TrieNode() { children = new TrieNode[26]; flag = false; for (int i = 0; i < 26; i++) { children[i] = null; } } } TrieNode root; /** Initialize your data structure here. */ public Trie() { root = new TrieNode(); } /** Inserts a word into the trie. */ public void insert(String word) { char[] array = word.toCharArray(); TrieNode cur = root; for (int i = 0; i < array.length; i++) { //当前孩子是否存在 if (cur.children[array[i] - 'a'] == null) { cur.children[array[i] - 'a'] = new TrieNode(); } cur = cur.children[array[i] - 'a']; } //当前节点代表结束 cur.flag = true; } /** Returns if the word is in the trie. */ public boolean search(String word) { char[] array = word.toCharArray(); TrieNode cur = root; for (int i = 0; i < array.length; i++) { //不含有当前节点 if (cur.children[array[i] - 'a'] == null) { return false; } cur = cur.children[array[i] - 'a']; } //当前节点是否为是某个单词的结束 return cur.flag; } /** * Returns if there is any word in the trie that starts with the given * prefix. */ public boolean startsWith(String prefix) { char[] array = prefix.toCharArray(); TrieNode cur = root; for (int i = 0; i < array.length; i++) { if (cur.children[array[i] - 'a'] == null) { return false; } cur = cur.children[array[i] - 'a']; } return true; } };
总
只要知道每个节点存字母,路径代表单词,代码就很好写出来了。
前缀树适用于两个场景。
统计每个单词出现的次数,代码的话只需要将上边的
flag
改成int
类型,然后每次插入的时候计数即可。当然,我们用
HashMap
也可以做到,key
是单词,value
存这个单词出现的次数即可。但缺点是,当单词很多很多的时候,受到Hash
函数的影响,hash
值会经常出现冲突,算法可能退化为O(n)
,n
是key
的总数。而对于前缀树,我们查找一个单词出现的次数,始终是
O(m)
,m
为单词的长度。查找某个前缀的单词,最常见的比如搜索引擎的提示、拼写检查、ip 路由等。