前缀和+字典树 208. 实现 Trie (前缀树) 字典树+dfs 212. 单词搜索 II

36 篇文章 0 订阅
2 篇文章 0 订阅

208. 实现 Trie (前缀树)

实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作。

示例:

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

说明:

你可以假设所有的输入都是由小写字母 a-z 构成的。
保证所有输入均为非空字符串。

解题
数据结构:字典树
(1)isend表示该节点是否为一个单词的终止节点;
(1)Trie每个节点保存一个字母表,保存字母树的单词前缀;

将每个字符保存在一个节点上;

class Trie {
private:
    bool isEnd;
    Trie* next[26];
public:
    Trie() {
        isEnd = false;
        memset(next, 0, sizeof(next));
    }
    
    void insert(string word) {
        Trie* node = this;
        for (char c : word) {
            if (node->next[c-'a'] == NULL) {
                node->next[c-'a'] = new Trie();
            }
            node = node->next[c-'a'];
        }
        node->isEnd = true;
    }
    
    bool search(string word) {
        Trie* node = this;
        for (char c : word) {
            node = node->next[c - 'a'];
            if (node == NULL) {
                return false;
            }
        }
        return node->isEnd;
    }
    
    bool startsWith(string prefix) {
        Trie* node = this;
        for (char c : prefix) {
            node = node->next[c-'a'];
            if (node == NULL) {
                return false;
            }
        }
        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);
 */

字典树模板:将单词保存在叶节点

class Trie {
private:
    string word="";
    vector<Trie *> next;
public:
    
    /** Initialize your data structure here. */
    Trie():next(26,0){}  //构造函数
    
    /** Inserts a word into the trie. */
    void insert(string word) {
        Trie *root=this;
        for(char c:word){
            if(!root->next[c-'a'])    root->next[c-'a']=new Trie;
            root=root->next[c-'a'];
        }
        root->word=word;  //单词只放在最后面
    }
    
    /** Returns if the word is in the trie. */
    bool search(string word) {
        Trie * tmp=this;
        for(char s:word)
            if(tmp->next[s-'a'])
                tmp=tmp->next[s-'a'];
            else return false;
        return tmp->word!="";
    }
    
    /** Returns if there is any word in the trie that starts with the given prefix. */
    bool startsWith(string prefix) {
        Trie * tmp=this;
        for(char s:prefix)
            if(tmp->next[s-'a'])
                tmp=tmp->next[s-'a'];
            else return false;
        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);
 */

212. 单词搜索 II

字典树+回溯
给定一个二维网格 board 和一个字典中的单词列表 words,找出所有同时在二维网格和字典中出现的单词。

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

示例:

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

输出: ["eat","oath"]
说明:

你可以假设所有输入都由小写字母 a-z 组成。

提示:

你需要优化回溯算法以通过更大数据量的测试。你能否早点停止回溯?
如果当前单词不存在于所有单词的前缀中,则可以立即停止回溯。什么样的数据结构可以有效地执行这样的操作?散列表是否可行?为什么? 前缀树如何?如果你想学习如何实现一个基本的前缀树,请先查看这个问题: 实现Trie(前缀树)。

解题
利用字典树trie存放每一个单词;
添加条件: 当trie中search到当前单词,则加入结果数组;(找到档次后需酱trie对象中该单词的isend标为false,避免重复添加);
剪枝: 每次回溯时查看是否为前缀(startswith),否则可以提前退出(推出前记得取消visited和cur);

class Trie {
private:
    bool isEnd;
    Trie * next[26];
public:
    
    /** Initialize your data structure here. */
    Trie() {
        isEnd=false;
        memset(next,0,sizeof next);
    }
    
    /** Inserts a word into the trie. */
    void insert(string word) {
        Trie * tmp=this;
        for(char s:word)
        {
            if(!tmp->next[s-'a'])
                tmp->next[s-'a']=new Trie;
            tmp=tmp->next[s-'a'];
        }
        tmp->isEnd=1;
    }
    
    /** Returns if the word is in the trie. */
    bool search(string word) {
        Trie * tmp=this;
        for(char s:word)
            if(tmp->next[s-'a'])
                tmp=tmp->next[s-'a'];
            else return false;
            if(tmp->isEnd){
                tmp->isEnd=false;           //找到后改为false,避免重复添加单词
                return true;
            }
        return false;
    }
    
    /** Returns if there is any word in the trie that starts with the given prefix. */
    bool startsWith(string prefix) {
        Trie * tmp=this;
        for(char s:prefix)
            if(tmp->next[s-'a'])
                tmp=tmp->next[s-'a'];
            else return false;
        return true;
    }
};

class Solution {
public:
    vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {
        m=board.size();
        n=board[0].size();
        visited.resize(m,vector<int>(n,0));
        for(string s:words)
            T.insert(s);
        next={{0,1},{0,-1},{1,0},{-1,0}};
        cur="";
        string tmp="";
        for(int i=0;i<m;i++)
            for(int j=0;j<n;j++)
                if(T.startsWith(tmp+board[i][j]))
                    dfs(board,i,j);
        return res;
    }

private:
    Trie T;
    vector<vector<int>> visited;
    vector<string> res;
    vector<vector<int>> next;
    string cur;
    int n,m;
    void dfs(vector<vector<char>>& board,int x,int y){
        if(visited[x][y]) return;
        visited[x][y]=1;
        cur+=board[x][y];
        if(T.search(cur)) res.push_back(cur);
        
        if(!T.startsWith(cur)) {  //退回时要消除记录
            visited[x][y]=0;
            cur.erase(cur.end()-1);
            return;
        }

        
        for(int i=0;i<4;i++)
        {
            int x1=x+next[i][0];
            int y1=y+next[i][1];
            if(x1>=0&&x1<m&&y1>=0&&y1<n){
                dfs(board,x1,y1);
            }
        }
        visited[x][y]=0;
        cur.erase(cur.end()-1);
        return;
    }
};

优化代码:字典树改
字典树,单词直接添加到字典树末尾的word中;
结束条件: 沿着当前字典树能找到字符串,添加该字符串至结果;
省去visited列表: 修改board标记已遍历的坐标;
去重: 字符串添加至结果后,该位置的word清空,防止重复结果;

class TrieNode{
public:
    string word = "";
    vector<TrieNode*> nodes;
    TrieNode():nodes(26, 0){}
};

class Solution {
    int rows, cols;
    vector<string> res;
public:
    vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {
        rows = board.size();
        cols = rows ? board[0].size():0;
        if(rows==0 || cols==0) return res;

        //建立字典树的模板
        TrieNode* root = new TrieNode();
        for(string word:words){
            TrieNode *cur = root;
            for(int i=0; i<word.size(); ++i){
                int idx = word[i]-'a';
                if(cur->nodes[idx]==0) cur->nodes[idx] = new TrieNode();
                cur = cur->nodes[idx];
            }
            cur->word = word;
        }

        //DFS模板
        for(int i=0; i<rows; ++i){
            for(int j=0; j<cols; ++j){
                dfs(board, root, i, j);
            }
        }
        return res;
    }

    void dfs(vector<vector<char>>& board, TrieNode* root, int x, int y){
        char c = board[x][y];
        //递归边界
        if(c=='.' || root->nodes[c-'a']==0) return;
        root = root->nodes[c-'a'];
        if(root->word!=""){
            res.push_back(root->word);
            root->word = "";
        }
        
        board[x][y] = '.';
        if(x>0) dfs(board, root, x-1, y);
        if(y>0) dfs(board, root, x, y-1);
        if(x+1<rows) dfs(board, root, x+1, y);
        if(y+1<cols) dfs(board, root, x, y+1);
        board[x][y] = c;
    }  
};
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值