字典树相关的算法题目

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 。

示例:

输入
["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 {

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 中可能包含一些 '.' ,每个 . 都可以表示任何一个字母

示例:

输入:
["WordDictionary","addWord","addWord","addWord","search","search","search","search"]
[[],["bad"],["dad"],["mad"],["pad"],["bad"],[".ad"],["b.."]]
输出:
[null,null,null,null,false,true,true,true]

解释:
WordDictionary wordDictionary = new WordDictionary();
wordDictionary.addWord("bad");
wordDictionary.addWord("dad");
wordDictionary.addWord("mad");
wordDictionary.search("pad"); // 返回 False
wordDictionary.search("bad"); // 返回 True
wordDictionary.search(".ad"); // 返回 True
wordDictionary.search("b.."); // 返回 True

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

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:

输入:board = [["o","a","a","n"],["e","t","a","e"],["i","h","k","r"],["i","f","l","v"]], words = ["oath","pea","eat","rain"]
输出:["eat","oath"]

示例 2:

输入:board = [["a","b"],["c","d"]], words = ["abcb"]
输出:[]

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

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;        
    }
};

4. 回文对

336. 回文对

给定一组 互不相同 的单词, 找出所有 不同 的索引对 (i, j),使得列表中的两个单词, words[i] + words[j] ,可拼接成回文串。

示例 1:

输入:words = ["abcd","dcba","lls","s","sssll"]
输出:[[0,1],[1,0],[3,2],[2,4]] 
解释:可拼接成的回文串为 ["dcbaabcd","abcddcba","slls","llssssll"]

示例 2:

输入:words = ["bat","tab","cat"]
输出:[[0,1],[1,0]] 
解释:可拼接成的回文串为 ["battab","tabbat"]

示例 3:

输入:words = ["a",""]
输出:[[0,1],[1,0]]

提示:

  • 1 <= words.length <= 5000
  • 0 <= words[i].length <= 300
  • words[i] 由小写英文字母组成

第一次解题,自然想到的是穷举法:

class Solution {
public:
    bool checkstring(string s) // 检测是否回文
    {
        if(s.length() <= 0) return true;
        int left = 0;
        int right = s.length()-1;
        while(left < right)
        {
            if(s[left] != s[right]) return false;
            ++left;
            --right;
        }
        return true;
    }
    vector<vector<int>> palindromePairs(vector<string>& words) {
       vector<vector<int>> ret;
       map<string, bool> record;
       for(int i = 0; i < words.size(); ++i)
       {
           for(int j = i+1; j < words.size(); ++j)
           {
               string s = words[i]+words[j];
               if(record.count(s) == 1)
               {
                   if(record[s])
                   {
                       ret.push_back({i,j});
                   }
               }
               else
               {       
                if(checkstring(s))
                {
                    ret.push_back({i,j});
                    record[s] = true;
                }
                else
                {
                    record[s] = false;
                }
               }
                s.clear();

               s = words[j]+words[i];
                if(record.count(s) == 1)
               {
                   if(record[s])
                   {
                       ret.push_back({j,i});
                      
                   }
               }
               else
               {
                if(checkstring(s))
                {
                    ret.push_back({j,i});
                     record[s] = true;
                }
                else
                {
                    record[s] = false;
                }
               }
           }
       }
       return ret;
    }
};

可想而知,提交结果是超时的。

应该怎么优化?

还是要用到字典树:

//字典树节点
class TrieNode
{
private:
    bool isEnd;//单词结束标记
    int index;//单词序号
    vector<TrieNode*> children;//子节点
public:
    //构造
    TrieNode():index(-1),isEnd(false),children(26,nullptr){}
    //析构
    ~TrieNode()
    {
        for(int i = 0;i < 26;i++)
        {
            if( children[i]) 
            {
                delete children[i];
                children[i] = nullptr;
            }
        }
    }
    //对外接口
    int getIndex() { return index;}
    void setIndex( int i) { index = i;}
    bool isWordEnd() { return isEnd;}
    void SetEnd(){ isEnd = true ;}
    //插入一个字符到子节点
    TrieNode * insertNode(char c)
    {
        if( !( 'a' <= c <= 'z')) return nullptr;
        int id = c-'a';
        if( children[id] == nullptr)
        {
            children[id] = new TrieNode();
            
        }
        return children[id];
    }
    //在子节点中查找一个字符
    TrieNode * getNode(char c)
    {
         if( !( 'a' <= c <= 'z')) return nullptr;
         int id = c-'a';
         return children[id] ;
    }
};
//字典树
class Trie
{
private:
    TrieNode * root;//根节点
public:
    Trie():root(new TrieNode()){}
    ~Trie()  { delete root;}  
    //插入一个单词及序号
    void insert( string word,int index)
    {
        TrieNode * p = root;
        for( int i = 0;i<word.size();i++)
        {
            p = p->insertNode(word[i]);
        }
        p->SetEnd();
        p->setIndex(index);
    }
    //查找一个字符串
    TrieNode *getNode(string word)
    {
        TrieNode * p = root;
        for(int i = 0;i < word.size();i++ )
        {
            p = p->getNode(word[i]) ;
            if( p == NULL ) return NULL;
        }
        return p;
    }
    //查找一个单词,返回序号
    bool  search(string word,int  &index)
    {
      
        TrieNode * p = getNode(word);
        if( p )
        {
            index = p->getIndex();
            return  p->isWordEnd();
        }
        return false;
    }
    

};
class Solution {
public:
    vector<vector<int>> palindromePairs(vector<string>& words) {
        vector<vector<int>> res;
        //构建字典树
        Trie * trieTree = new Trie();
        for(int i = 0;i < words.size();i++)
        {
            trieTree->insert(words[i],i);
        }
        for(int i = 0;i < words.size();i++)
        {
            for(int j = 0;j < words[i].size();j++ )
            {
                bool flag = check(words[i],0,j);
                if(flag)//前半截是回文
                {
                    string temp = words[i].substr(j+1);
                    reverse(temp.begin(),temp.end());
                    int index = -1;
                    if( trieTree->search(temp,index) )
                    {
                        if( i != index  )
                        {
                             res.push_back({index,i});
                             if( temp == "")
                             {
                                 res.push_back({i,index});
                             }
                        }
                           
                    }
                }
                flag = check(words[i],j+1,words[i].size()-1);
                if(flag)//后半截是回文
                {
                        string temp = words[i].substr(0,j+1);
                        reverse(temp.begin(),temp.end());
                        int index = -1;
                        if( trieTree->search(temp,index) )
                        {
                            if( i != index  )
                                res.push_back({i,index});
                        }
                }    
            }
        }
        return res;
    }
    bool check(string &vec,int left,int right)
    {
        int i = left;
        int j = right;
        while(i <= j)
        {
            if( vec[i] != vec[j]) return false;
            i++;
            j--;
        }
        return true;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值