07字典树
# 05 字典树
字典树一般用于判断字符串是否存在或者是否具有某种字符串前缀。如下图一个字典树中,存储了 A、to、tea、ted、ten、i、in 和 inn,以及它们的频率。

为什么需要用字典树解决这类问题呢?假如我们有一个储存了近万个单词的字典,即使我们使用哈希,在其中搜索一个单词的实际开销也是非常大的,且无法轻易支持搜索单词前缀。然而由于一个英文单词的长度 n 通常在 10 以内,如果我们使用字典树,则可以在 O(n)——近似 O(1)的时间内完成搜索,且额外开销非常小。
# 208 实现 Trie (前缀树) (opens new window)
尝试建立一个字典树,支持快速插入单词、查找单词、查找单词前缀的功能
以下是数据结构的调用样例
输入: ["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
class Trie {
private:
vector<Trie*> children;
bool isEnd;
Trie* searchPrefix(string prefix) {
Trie* node = this;
for (char ch : prefix) {
ch -= 'a';
if (node->children[ch] == nullptr) {
return nullptr;
}
node = node->children[ch];
}
return node;
}
public:
Trie() : children(26), isEnd(false) {}
void insert(string word) {
Trie* node = this;
for (char ch : word) {
ch -= 'a';
if (node->children[ch] == nullptr) {
node->children[ch] = new Trie();
}
node = node->children[ch];
}
node->isEnd = true;
}
bool search(string word) {
Trie* node = this->searchPrefix(word);
return node != nullptr && node->isEnd;
}
bool startsWith(string prefix) {
return this->searchPrefix(prefix) != nullptr;
}
};
# 820 单词的压缩编码 (opens new window)
单词数组 words 的 有效编码 由任意助记字符串 s 和下标数组 indices 组成,且满足:
words.length == indices.length
- 助记字符串 s 以 '#' 字符结尾
- 对于每个下标 indices[i] ,s 的一个从 indices[i] 开始、到下一个 '#' 字符结束(但不包括 '#')的 子字符串 恰好与 words[i] 相等
给定一个单词数组 words ,返回成功对 words 进行编码的最小助记字符串 s 的长度 。
输入:words = ["time", "me", "bell"]
输出:10
解释:一组有效编码为 s = "time#bell#" 和 indices = [0, 2, 5] 。 words[0] = "time" ,s 开始于 indices[0] = 0 到下一个 '#' 结束的子字符串,如加粗部分所示 "time#bell#" words[1] = "me" ,s 开始于 indices[1] = 2 到下一个 '#' 结束的子字符串,如加粗部分所示 "time#bell#" words[2] = "bell" ,s 开始于 indices[2] = 5 到下一个 '#' 结束的子字符串,如加粗部分所示 "time#bell#"
解析:
class Trie{
private:
Trie* child[26];
bool found;
public:
Trie(){
for(int i=0;i<26;++i){
child[i] = nullptr;
found = false;
}
}
void insert(string word){
Trie* node = this;
for(const auto ch:word){
if(node->child[ch-'a'] == nullptr){
node->child[ch-'a'] = new Trie();
}
node = node->child[ch-'a'];
}
node->found = true;
}
bool search(string word){
Trie* node = this;
for(const auto ch:word){
if(node->child[ch-'a']==nullptr){
return false;
}
node = node->child[ch-'a'];
}
return node->found;
}
bool prefix(string prefix){
Trie* node = this;
for(const auto ch:prefix){
if(node->child[ch-'a']==nullptr){
return false;
}
node = node->child[ch-'a'];
}
return true;
}
};
class Solution {
public:
int minimumLengthEncoding(vector<string>& words) {
unordered_set<string> hash;
// 要从长单词到短单词构建字典树
sort(words.begin(),words.end(),[](string& a, string& b){
return a.length() > b.length();
});
Trie* trie = new Trie();
// 遍历所有单词,如果已经存在字典树中则直接跳过,反之生成插入新单词
for(string& word:words){
reverse(word.begin(),word.end()); // 后缀检验和插入
if(trie->prefix(word)){
continue;
}
trie->insert(word);
hash.insert(word);
}
// 计算字典树中表征的单词个数
int ans = 0;
for(auto word:hash){
ans += word.length()+1;
}
return ans;
}
};