Trie 小结

95 阅读4分钟

Trie 前缀树 prefix tree, 又称字典树。它用一个树状的数据结构储存一个字典中的所有单词。是典型的空间换时间的思想。prefix tree is called prefix becaues it is easy to check the prefix of the word.

网上有很多讲Trie的视频和文章,但看完只会对Trie有个非常模糊的认识。跟着下面的习题做完,才能熟悉Trie,解决Trie。习题选择来自剑指offer第十章 前缀树 以及needcode, blind75

前序

视频

例题

Question 1

208. Implement Trie (Prefix Tree) Blind 75

Neetcode Explanation

题解来自neetcode c++ 中国区力扣解释微难理解一些。neetcode更好懂

class TrieNode{
    public: 
        TrieNode* children[26];
        bool isWord;

        TrieNode(){
            for(int i = 0; i < 26; i++){
                children[i] = nullptr;
            }
            isWord = false;
        }
};

class Trie {
private:
    TrieNode* root;    
public:
    Trie() {
        root = new TrieNode();
    }
    
    void insert(string word) {
        //1. 子节点存在,沿着指针移动到子节点,继续处理下一个字符
        //  2.子节点不存在,创建一个新的子节点,记录在children数组的对应位置上,
        //    沿着指针移动到子节点,继续搜索下一个字符
        //  3.重复以上步骤,直到处理字符串的最后一个字符,将最后一个字符的节点标记为字符串的结尾
        TrieNode* node = root;
        int curr = 0;

        for(int i = 0; i < word.size(); i++){
            curr = word[i] - 'a';
            if(node->children[curr] == nullptr){
                 node->children[curr] = new TrieNode();
            }
            node = node->children[curr];
        }
        node->isWord = true; //last node mark as true 
        //can have as many isWord true as possible in one line since prefixes can have same prefixes. 
    }
    
    bool search(string word) {
        /* 子节点存在,沿着指针移动到子节点,继续搜索下一个字符
           子节点不存在,说明字典树不包含该前缀,返回false
           重复以上步骤,如果搜索完word的最后一个字符且最后节点的isEnd为真,字典树确实存在该字符串
        */
        TrieNode* node = root;
        int curr = 0;

        for(int i = 0; i < word.size(); i++){
            curr = word[i] - 'a';
            if(node->children[curr] == nullptr){
                return false;
            }
            node = node->children[curr];
        }
        return node->isWord; //make sure the end of the node should be the end.
    }
    
    bool startsWith(string prefix) {
        TrieNode* node = root;
        int curr = 0;

        for(int i = 0; i < prefix.size(); i++){
            curr = prefix[i] - 'a';
            if(node->children[curr] == nullptr){
                return false;
            }
            node = node->children[curr];
        }
        return true;
    }
};

同时,这里因为leetcode自身设计,不用回收废弃指针,但在现实oop中,对于c++一定要注意指针的回收.比如linkedList 等都是一样

有些题解,不是用的TreeNode* children[26]来作为底的Trie class。而是使用

struct Trie {
    unordered_map<char, Trie *> children;
};

他标记最后字母节点的方式是:

cur->children['#'] = new Trie();

比如中区648. 单词替换 题解二。很有意思,推荐阅读。

其他问题

648. Replace Words 看完第一题(LC208)细心做就能得出正确答案。- 来自NYU Tandon LeetCode Bootcamp

class Solution {

public:
    class TrieNode{
    public:    
        TrieNode* children[26];
        bool isWord;
        TrieNode(){
            for(int i = 0; i < 26; i++){
                children[i] = nullptr;
            }
            isWord = false;
        }
    };
    class Trie{
    public:    
        TrieNode* root;
        
        Trie(){
            root = new TrieNode();
        }

        void insert(string word){
            TrieNode* node = root;
            for(char c: word){
                int curr = c - 'a';
                if(node->children[curr] == nullptr) 
                    node->children[curr] = new TrieNode();
                node = node->children[curr];    
            }
            node->isWord = true;
        }

        string search(string word){
            string path;
            TrieNode* node = root;
            for(char c: word){
                int curr = c - 'a';
                if(node->children[curr] == nullptr || node->isWord) break;
                path += c;
                node = node->children[curr]; 
            }
            return node->isWord == true? path:word;
        }
    };
    string replaceWords(vector<string>& dictionary, string sentence) {
        if(dictionary.size() == 0) return sentence;
        if(sentence.size() == 0) return "";

        Trie dic;
        for(string str: dictionary){
            dic.insert(str);
        }

        //separate the sentence into the words vector
        vector<string> separated; 
        int i = 0;
        string curr = "";
        while(i < sentence.size()){
            if(sentence[i] == ' '){
                separated.push_back(curr);
                curr = "";
            } else{
                curr += sentence[i]; 
            }
            i++;
        }
        separated.push_back(curr);
        
        string ans = "";
        for(string str: separated){
            
            ans = ans + dic.search(str) + " ";
        }
        ans.pop_back();
        return ans;
    }
};

211. Design Add and Search Words Data Structure Blind 75 208变种题 dfs related. 题解看neetcode. 真是贼有意思的题。和dfs结合考,哪个不会都做不出来。# 79. 单词搜索 也是和dfs结合考。也没第一遍做出来。dfs真的需要重新学习一下了。tree常考dfs

class TrieNode{
public: 
    TrieNode* children[26];
    bool isWord;

    TrieNode(){
       for(int i = 0; i < 26; i++){
           children[i] = nullptr;
       }
       isWord = false;
    } 
};
class WordDictionary {
private:
    TrieNode* root;    
public:
    WordDictionary() {
        root = new TrieNode();
    }
    
    void addWord(string word) {
        //add word is normal as TrieNode add.
        TrieNode* node = root;
        int curr = 0;
        for(int i = 0; i < word.size(); i++){
            curr = word[i] - 'a';
            if(node->children[curr] == nullptr){
                node->children[curr] = new TrieNode();
            }
            node = node->children[curr];
        }
        node->isWord = true;
    }
    
    bool search(string word) {
        //there exists '.'. dots can be matched with any letter. 
        TrieNode* node = root;
        return searchInNode(word, 0, node);  
    }

private:
    bool searchInNode(string& word, int i, TrieNode* node){
        if(node == nullptr) return false;
        if(i == word.size()) return node->isWord;
        if(word[i] != '.') return searchInNode(word, i+1, node->children[word[i]-'a']);
        for(int j = 0; j < 26; j++){
            if(searchInNode(word, i+1, node->children[j])){
                return true;
            }
        }
        return false;
    }    
};

复习这道题的时候在反应dfs,dfs就是recursive function. 就像树一样。

139. Word Break Blind75 1-dynamic中的题 用trie解会节约时间。

212. Word Search II extra: 回溯经典题 79. Word Search Blind 75

Leetcode 720 Leetcode 692

Extra materials

Recommended by NYU Leetcode Bootcamp recommended by instructor Spriha Jha

Leetcode Trie 集合

Understanding Trie