0
点赞
收藏
分享

微信扫一扫

字典树相关的算法题目

静守幸福 2022-03-26 阅读 52
算法c++

1. 字典树的实现

208. 实现 Trie (前缀树)

请你实现 Trie 类:

Trie() :初始化前缀树对象。
void insert(String word): 向前缀树中插入字符串 word 。
boolean search(String word): 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false 。
boolean startsWith(String prefix) :如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。

示例:

class Trie {

public:
    class node
    {
    private:
        vector<node*> next;
        bool is_end;
    public:
        node():next(26, nullptr),is_end(false)
        {
        }
        ~node()
        {
            for(auto i : next)
            {
                if(i)
                {
                    delete i;
                    i = nullptr;
                }
            }
        }
        node* insert(char v)
        {
            int idx = v-'a';
            if(next[idx] == nullptr)
            {
                next[idx] = new node();
            }
            return next[idx];
        }
        node* find(char v)
        {
            return next [v-'a'];
        }
        void setend(){is_end = true;}
        bool isend(){return is_end;}
    };
    Trie() {
        root = new node();
    }
    ~Trie() {
        delete root;
    }
    
    void insert(string word) {
        node* p = root;
        for(int i = 0; i < word.size(); ++i)
        {
            p = p->insert(word[i]);
        }
        p->setend();
    }
    
    bool search(string word) {
        node* p = root;
        for(int i = 0; i < word.size();++i)
        {
            p = p->find(word[i]);
            if(!p)
            {
                return false;
            }
        }
        if(!p->isend())
        {
            return false;
        }
        return true;
    }
    
    bool startsWith(string prefix) {
        node* p = root;
        for(int i = 0; i < prefix.size();++i)
        {
            p = p->find(prefix[i]);
            if(!p)
            {
                return false;
            }
        }
        return true;
    }
private:
    node *root;
};

/**
 * 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);
 */

提交结果:

 效果不是很理想,想想应该怎么优化?

首先想到的是node节点定义的是26个字母的指针,实际上不需要那么多,用的时候再创建能否节省一些空间?

用map结构代替vector结构试一下:

class Trie {

public:
    class node
    {
    private:
        map<char, node*>next;
        bool is_end;
    public:
        node():is_end(false)
        {
        }
        ~node()
        {
            for(map<char, node*>::iterator i = next.begin(); i != next.end(); ++i)
            {
                if(i->second)
                {
                    delete i->second;
                    i->second = nullptr;
                }
            }
        }
        node* insert(char v)
        {
            if(next.count(v) == 0)
            {
                next[v] = new node();
            }

            return next[v];
        }
        node* find(char v)
        {
            if(next.count(v) == 1)
            {
                return next[v];
            }
            else
            {
                return nullptr;
            }
        }
        void setend(){is_end = true;}
        bool isend(){return is_end;}
    };
    Trie() {
        root = new node();
    }
    ~Trie() {
        delete root;
    }
    
    void insert(string word) {
        node* p = root;
        for(int i = 0; i < word.size(); ++i)
        {
            p = p->insert(word[i]);
        }
        p->setend();
    }
    
    bool search(string word) {
        node* p = root;
        for(int i = 0; i < word.size();++i)
        {
            p = p->find(word[i]);
            if(!p)
            {
                return false;
            }
        }
        if(!p->isend())
        {
            return false;
        }
        return true;
    }
    
    bool startsWith(string prefix) {
        node* p = root;
        for(int i = 0; i < prefix.size();++i)
        {
            p = p->find(prefix[i]);
            if(!p)
            {
                return false;
            }
        }
        return true;
    }
private:
    node *root;
};

/**
 * 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);
 */

提交结果:

可见在空间上节省了很多,但是执行用时提高不是很明显。 

尝试把node节点操作挪到trie成员函数里吗,结果毫无变化。那么应该怎么提高执行效率?

最后通过题解的对比发现,还是用数组最快:

class Trie 
{
public:
    Trie * child[26];
    bool isword;

    /** Initialize your data structure here. */
    Trie() 
    {
        memset(child, NULL, sizeof(child));
        isword = false;
    }
    
    /** Inserts a word into the trie. */
    void insert(string word) 
    {
        Trie * rt = this;       /从根开始,相当于python3中的self
        for (char w: word)
        {
            int ID = w - 'a';
            if (rt->child[ID] == NULL)    //path断了
                rt->child[ID] = new Trie();  //新建
            rt = rt->child[ID];
        }
        rt->isword = true; 
    }
    
    /** Returns if the word is in the trie. */
    bool search(string word) 
    {
        Trie * rt = this;
        for (char w: word)
        {
            int ID = w - 'a';
            if (rt->child[ID] == 0)
                return false;
            rt = rt->child[ID];
        }
        return rt->isword == true;
    }
    
    /** Returns if there is any word in the trie that starts with the given prefix. */
    bool startsWith(string prefix) 
    {
        Trie * rt = this;
        for (char p: prefix)
        {
            int ID = p - 'a';
            if (rt->child[ID] == 0)
                return false;
            rt = rt->child[ID];
        }
        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);
 */

提交结果:

在空间和时间上效果都很好。

2. 添加和搜索单词

211. 添加与搜索单词 - 数据结构设计

请你设计一个数据结构,支持 添加新单词 和 查找字符串是否与任何先前添加的字符串匹配 。

实现词典类 WordDictionary :

WordDictionary() 初始化词典对象
void addWord(word) 将 word 添加到数据结构中,之后可以对它进行匹配
bool search(word) 如果数据结构中存在字符串与 word 匹配,则返回 true ;否则,返回  false 。word 中可能包含一些 '.' ,每个 . 都可以表示任何一个字母

示例:

这道题就是在上面的基础上加入树的深度遍历:

class WordDictionary {
public:
    WordDictionary* next[26];
    bool is_end;
    WordDictionary()
    {
        is_end = false;
        memset(next,NULL,sizeof(next));
    }
    ~WordDictionary()
    {
        for(int i= 0; i < 26; ++i)
        {
            if(next[i])
            {
                delete next[i];
            }
        }
    }    
    void addWord(string word) {
        WordDictionary *node = this;
        for(char ch:word)
        {
            ch -= 'a';
            if(node->next[ch] == NULL)
            {
                node->next[ch] = new WordDictionary();
            }
            node = node->next[ch];
        }
        node->is_end = true;
    }

    bool dfs(string word, int start,WordDictionary *node)
    {
        if(word.size() == start)
        {
            return node->is_end;
        }

        char ch = word[start];
        if(ch == '.')
        {
            for(int i = 0; i < 26; ++i)
            {
                if(node->next[i] && dfs(word, start+1, node->next[i]))
                {
                    return true;
                }
            }
        }
        else
        {
            ch -= 'a';
            if(node->next[ch] && dfs(word, start+1, node->next[ch]))
            {
                return true;
            }

            
        }
        return false;
    }
    
    bool search(string word) {
        return dfs(word,0,this);
    }
};

/**
 * Your WordDictionary object will be instantiated and called as such:
 * WordDictionary* obj = new WordDictionary();
 * obj->addWord(word);
 * bool param_2 = obj->search(word);
 */

提交结果:

 用时有点久。对比其他答案,把树节点单独定义出来,执行效率会高一些:


struct Node
{
    Node* children[26];
    // 表示这里是一个单词的结束
    bool isEnd; 

    Node()
    {
        memset(children, 0, sizeof(children));
        isEnd = false;
    }
};

class WordDictionary {
private:
    Node* root;
public:
    /** Initialize your data structure here. */
    WordDictionary() {
        root = new Node();
    }
    
    void addWord(string word) {
        Node* curr = root;
        for (char c : word)
        {
            int i = c - 'a';
            if (curr->children[i] == nullptr)
            {
                curr->children[i] = new Node();
            }
            curr = curr->children[i];
        }
        // 最后一个字符则设置 isEnd
        curr->isEnd = true;
    }
    
    bool dfs(string& word, int start, Node* curr)
    {
        // 已经遍历完,看是否是结束
        if (start == word.size())
        {
            return curr->isEnd;
        }
        
        // 对于点,就是去找所有可能的下一个结点,然后递归
        if (word[start] == '.')
        {
            for (int j = 0; j < 26; ++j)
            {
                if (curr->children[j] != nullptr && dfs(word, start+1, curr->children[j]))
                {
                    return true;
                }
            }
            return false;
        }
        else
        {
            // 其他情况就是找下一个结点
            int i = word[start] - 'a';
            if (curr->children[i] != nullptr)
            {
                return dfs(word, start+1, curr->children[i]);
            }
            else
            {
                return false;
            }
        }
    }

    bool search(string word) {
        // 递归来查找
        return dfs(word, 0, root);
    }
};

/**
 * Your WordDictionary object will be instantiated and called as such:
 * WordDictionary* obj = new WordDictionary();
 * obj->addWord(word);
 * bool param_2 = obj->search(word);
 */

提交结果:

 

3. 单词搜索

212. 单词搜索 II

给定一个 m x n 二维字符网格 board 和一个单词(字符串)列表 words, 返回所有二维网格上的单词 。

单词必须按照字母顺序,通过 相邻的单元格 内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。

示例 1:

示例 2:

解题思路是字典树+回溯法

struct TrieNode {
    string word;
    unordered_map<char, TrieNode *> children;
    TrieNode() {
        this->word = "";
    }   
};

void insertTrie(TrieNode * root, const string & word) {
    TrieNode * node = root;

    for (auto c : word) {
        if (!node->children.count(c)) {
            node->children[c] = new TrieNode();
        }
        node = node->children[c];
    }

    node->word = word;
}

class Solution {
public:
    int dirs[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};

    bool dfs(vector<vector<char>>& board, int x, int y, TrieNode * root, set<string> & res) {
        char ch = board[x][y];   
     
        if (root == nullptr || !root->children.count(ch)) {
            return false;
        }

        TrieNode * nxt = root->children[ch];
        if (nxt->word.size() > 0) {
            res.insert(nxt->word);
            nxt->word = "";
        }
        if (!nxt->children.empty()) {
            board[x][y] = '#';
            for (int i = 0; i < 4; ++i) {
                int nx = x + dirs[i][0];
                int ny = y + dirs[i][1];
                if (nx >= 0 && nx < board.size() && ny >= 0 && ny < board[0].size()) {
                    if (board[nx][ny] != '#') {
                        dfs(board, nx, ny, nxt,res);
                    }
                }
            }
            board[x][y] = ch;
        }
        if (nxt->children.empty()) {
            root->children.erase(ch);
        }

        return true;      
    }

    vector<string> findWords(vector<vector<char>> & board, vector<string> & words) {
        TrieNode * root = new TrieNode();
        set<string> res;
        vector<string> ans;

        for (auto & word: words) {
            insertTrie(root,word);
        }
        for (int i = 0; i < board.size(); ++i) {
            for(int j = 0; j < board[0].size(); ++j) {
                dfs(board, i, j, root, res);
            }
        }        
        for (auto & word: res) {
            ans.emplace_back(word);
        }
        
        return ans;        
    }
};

举报

相关推荐

0 条评论