LeetCode - 208. Implement Trie (Prefix Tree)
LeetCode - 648. Replace Words
Trie, 又称前缀树或字典树,是一种有序树,用于保存关联数组,其中的键通常是字符串。与二叉查找树不同,键不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串。一般情况下,不是所有的节点都有对应的值,只有叶子节点和部分内部节点所对应的键才有相关的值。
Trie 这个术语来自于 retrieval。如下图,键标注在节点中,值标注在节点之下。每一个完整的英文单词对应一个特定的整数。Trie 可以看作是一个确定有限状态自动机,尽管边上的符号一般是隐含在分支的顺序中的。键不需要被显式地保存在节点中。图示中标注出完整的单词,只是为了演示trie的原理。
![](https://img-blog.csdnimg.cn/20190811192813190.png)
trie 中的键通常是字符串,但也可以是其它的结构。trie的算法可以很容易地修改为处理其它结构的有序序列,比如一串数字或者形状的排列。比如,bitwise trie 中的键是一串比特,可以用于表示整数或者内存地址。
LeetCode 的 solution 中讲了 Trie 的几种常见用途:
1、自动补全 - Autocomplete
![](https://img-blog.csdnimg.cn/20190811210030403.png)
2、拼写检查 - Spell checker
![](https://img-blog.csdnimg.cn/20190811210121334.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0JvYl9feXVhbg==,size_16,color_FFFFFF,t_70)
3、IP路由 - IP routing (Longest prefix matching)
![](https://img-blog.csdnimg.cn/2019081121024835.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0JvYl9feXVhbg==,size_16,color_FFFFFF,t_70)
4、9键键盘 - T9 predictive text
![](https://img-blog.csdnimg.cn/20190811210340240.png)
对 Trie 来说最常见的两个操作是 search for a key 和 insert a key 。实现 Trie,其实不难,每个节点除了存一个 26 个大小的自身类型的节点外,还需要存一个 isEndofWord 这种标志位,因为 good 和 goodness 两个词如果都有,没有标志位就不知道有 good 这个词了,所以 g、o、o、n、e、s 的 isEndofWord 都是 false,d 和最后一个 s 的 isEndofWord 是 true,这样就可以知道有 good 这个词了。所以其实 search 和 startsWith 是一样的,唯一的区别就是最后是不是结尾了。insert 也很简单,就是有这个字符的子节点,就往下,没有就创造一个,再往下。(下边的代码没有进行析构,不过AC了)
struct TrieNode {
TrieNode* children[26];
bool isEndofWord;
TrieNode() : isEndofWord(false) {
for(int i = 0; i < 26; ++i)
children[i] = nullptr;
};
};
class Trie {
public:
TrieNode* root;
Trie() {
root = new TrieNode();
}
void insert(string word) {
auto rt = root;
for(const auto& c : word) {
const int num = c - 'a';
if(rt->children[num] == nullptr)
rt->children[num] = new TrieNode();
rt = rt->children[num];
}
rt->isEndofWord = true;
}
bool search(string word) {
auto rt = root;
int i = 0;
while(i < word.length() && rt->children[word[i] - 'a'] != nullptr) {
rt = rt->children[word[i] - 'a'];
++i;
}
return i == word.length() && rt->isEndofWord;
}
bool startsWith(string prefix) {
auto rt = root;
int i = 0;
while(i < prefix.length() && rt->children[prefix[i] - 'a'] != nullptr) {
rt = rt->children[prefix[i] - 'a'];
++i;
}
return i == prefix.length();
}
};
LeetCode - 648. Replace Words
这道题如下,就是用最短前缀替换sentence中的单词,可以用 prefix tree 来实现:
Given a dictionary consisting of many roots and a sentence. You need to replace all the successor in the sentence with the root forming it. If a successor has many roots can form it, replace it with the root with the shortest length.
You need to output the sentence after the replacement.Example 1:
Input: dict = [“cat”, “bat”, “rat”] sentence = “the cattle was rattled by the battery”
Output: “the cat was rat by the bat”
struct TrieNode {
bool isEndofWord;
TrieNode * children[26]{};
TrieNode() : isEndofWord(false) {
for (auto& c : children)
c = nullptr;
}
};
struct Trie {
TrieNode* root;
Trie() {
root = new TrieNode();
}
void insert(const string& s) {
auto r = root;
for (const char& c : s) {
if (r->children[c - 'a'] == nullptr)
r->children[c - 'a'] = new TrieNode();
r = r->children[c - 'a'];
}
r->isEndofWord = true;
}
string findShortestPrefix(const string& s) {
auto r = root;
for(int i = 0; i < s.length(); ++i)
{
if (r->children[s[i] - 'a'] == nullptr)
return s;
r = r->children[s[i] - 'a'];
// 找到一个prefix就返回,肯定是最短的
if (r->isEndofWord)
return s.substr(0, i + 1);
}
return s;
}
};
string replaceWords(const vector<string>& dict, string sentence) {
Trie tri;
for (const auto& word : dict)
tri.insert(word);
bool first = true;
string res, sen;
stringstream ss;
ss << sentence;
while (ss >> sen) {
if (first) {
res = tri.findShortestPrefix(sen);
first = false;
}
else
res += " " + tri.findShortestPrefix(sen);
}
return res;
}