题目描述:
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 * 104 次来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/implement-trie-prefix-tree
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
分析:
这道题并不会写,甚至连题目都看不懂,最后无奈之下看了题解。
这道题,本质上是一个26叉树,每个节点伸出来26个分枝,每个分支从1-26分别对应字母a~z,并且节点上还有一个标志位isend,代表该节点是否为某个插入单词的结束点。当插入第一个字符串“word”时,首先需要存放‘w’,我们令当前节点的第23个分支(字母w对应的那个)指向一个新节点(节点指向不为空,意味着该分支对应的字母存在),移动到新节点上。接下来要插入字符串“ord”,则找到o对应的分支,指向一个新节点。。。直到字母“d”也插入完毕。当字母d插入完毕时,在插入字母d时新建的节点上,令其isend为true,代表该节点时某个插入字符串的结尾。
查找则是沿着根节点开始,查找字符串的第一个字母,查看对应的分枝是否有节点,如果没有,则说明不存在,返回false;如果有,则继续查找下一个字母,直至查找完毕。并且在查找完毕时判断该节点标志位isend是否为true,如果不是,说明该字符串没有被插入(能查找到只是因为该字符串是某个插入字符串的子串),返回false;如果isend为true,则说明该查找字符串曾经被插入过,返回true
查找子串跟上面流程差不多,只是不用再判断isend标志位了。
详细的分析请看题解:
代码如下:
class Trie {
public:
// isend代表该节点是否为某个单词的结束点
bool isend;
// 26叉树,分别指向a,b,c。。。。x,y,z
Trie * next[27];
Trie() {
// 初始化isend为false
this->isend=false;
// 初始化next为NULL
memset(this->next,NULL,sizeof(this->next));
}
void insert(string word) {
// 获取当前节点
Trie *node=this;
for(int i=0;i<word.size();i++)
{
// 计算word[i]在字母表中的位置,a=1,b=2...z=26
int index=word[i]-'a'+1;
// 判断是否需要新建节点
if(node->next[index]==NULL)
node->next[index]=new Trie();
// 移动到下一个节点
node=node->next[index];
}
// 插入完毕,该节点是某个插入字符串的结尾
node->isend=true;
}
bool search(string word) {
// 获取当前节点
Trie *node=this;
for(int i=0;i<word.size();i++)
{
int index=word[i]-'a'+1;
// 如果下个节点不为空,则继续查找
if(node->next[index]!=NULL)
node=node->next[index];
// 下个节点为空,则说明该字符串没有被插入
else
return false;
}
// 判断查找结束后的节点是否为某个插入字符串的结尾
if(node->isend==true)
return true;
else
return false;
}
bool startsWith(string prefix) {
// 获取当前节点
Trie *node=this;
for(int i=0;i<prefix.size();i++)
{
int index=prefix[i]-'a'+1;
// 如果下个节点不为空,则继续查找
if(node->next[index]!=NULL)
node=node->next[index];
// 下个节点为空,则说明该前缀不存在
else
return false;
}
return true;
}
};
/**
* Your Trie object will be instantiated and called as such:
* Trie* obj = new Trie();
* obj->insert(word);
* bool param_2 = obj->search(word);
* bool param_3 = obj->startsWith(prefix);
*/
于我而言,这道题让我学会了很多知识
1.c++中的this
2.构造函数中的this及初始化
3.声明类数组
4.c++实例变量
5.类方法中的递归
以上感悟请看另一篇文章:
https://blog.csdn.net/weixin_43098069/article/details/123686292