题目地址
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”, “insert”, “search”, “search”, “startsWith”, “insert”, “search”]
[[], [“apple”], [“apple”], [“app”], [“app”], [“app”], [“app”]]
输出:
[null, null, true, false, true, null, true]
解释
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
提示:
1 <= word.length, prefix.length <= 2000
word 和 prefix 仅由小写英文字母组成
insert、search 和 startsWith 调用次数总计不超过 3 * 10^4 次
参考:指路
前缀树,又称字典树、Trie树。顾名思义,这是一个树结构,来快速检索前缀。最广泛的应用就是这里面的search操作,当你在检索的时候,搜索了某个前缀,搜索框就会自动给出一些高频出现的,以这个为前缀的单词,用户就不需要打全整个单词了。
我们看Trie的结构,如下,假设我们现在单词表中有[dog,dot,one,do,ok]。
我们首先定义一个root结点作为开始,然后把五个单词每个字符按照顺序加入树。**每一个树节点的True表示从根节点到这个节点组成的单词在单词表中,我们可以知道,所有的叶节点都为True。**我们搜索do,发现最后o对应的结点为True,所以do这个单词存在。我们搜索on,n这个节点为Fasle,这个单词不存在。
对于insert操作,我们只要从根节点开始,按顺序对单词每个字符结点加入即可(如果已经存在了就忽略),对单词的最后一个字符的结点置为True。
对于startswith操作,我们只要判断这个前缀是不是按顺序存在即可,不需要考虑True和False,比如前缀on是存在的。
解法:字典(哈希表)
对于每个节点都是一个字典dict,它的键为后续的字符,值为这个字符对应的字典,同时对单词结尾的字符,我们添加一个键-1,如果-1在这个字典中,说明这个单词存在。整个结构相当于一层一层嵌套的字典,所以叫字典树。
代码实现:
class Trie:
def __init__(self):
"""
Initialize your data structure here.
"""
#初始化根节点
self.root = dict()
def insert(self, word: str) -> None:
p = self.root # p是临时变量,通过p改变root(也就是树)
#遍历word中所有字符
for x in word:
#如果x已经存在与p的键中了,说明x已经为p的后续字符了,pass
#不存在,则对字符x创建一个字典,这个字典作为p中x对应的值
if x not in p:
p[x] = dict() # 插入app的时候不会进来这个if语句,因为已经有app这3个字符了
p = p[x]
# 插入apple的过程中全都是: p[x]:{}; p:{}。
# 插入app:
# x:a; p: {'p': {'p': {'l': {'e': {-1: True}}}}}
# x:p; p: {'p': {'l': {'e': {-1: True}}}}
# x:p; p {'l': {'e': {-1: True}}}
#单词最后一个字符的单词,加一个键-1,对应值存不存在都无所谓,只要-1这个键存在,就代表是单词结尾
p[-1] = True
# 插入apple完成后: p:{-1: True},root:{'a': {'p': {'p': {'l': {'e': {-1: True}}}}}};
# 插入app完成后: p:{'l':{'e':{-1:True}},-1:True},root:{'a': {'p': {'p': {'l': {'e': {-1: True}}, -1: True}}}},注意字母l和-1是同级的,也就是app后面跟l或-1
def search(self, word: str) -> bool:
p = self.root
#开始遍历
for x in word:
if x in p:
p = p[x]
#如果路上不存在这个字符,直接Fasle
else:
return False
#遍历到最后,还需要看-1存不存在
if -1 in p:
return True
else:
return False
def startsWith(self, prefix: str) -> bool:
"""
Returns if there is any word in the trie that starts with the given prefix.
"""
#与上面search一样,只不过不需要看最后-1存不存在,因为不是找单词,是找前缀
p = self.root
for x in prefix:
if x in p:
p = p[x]
else:
return False
return True