LeetCode212. 单词搜索 II

这题是LeetCode79. 单词搜索的进阶版,第79题是在二维的char数组里搜索单个单词,
这题需要在二维char数组里搜索一组单词。

理论上只需要对遍历单词数组,逐个套用79的dfs方法即可,不过这题题目要求了需要用Trie树做优化。

所以,我们最开始可以把单词列表里的所有单词都插入到一个Trie树中,然后对单词做搜索的时候,除了检查下一个位置的字母不越界且未使用过(题目要求
同一个单元格内的字母不允许被重复使用)以外,还需要检查下一个位置的字母是否是当前字母在trie树的孩子。

通过这个额外的Trie树的剪枝,可以减小搜索空间。

有几点需要注意:
(1)Trie树存储每个单词的时候,需要在每个单词的结尾字母打一个标记,我们用一个整数变量id来确定当前字母是第id个字母(id从0开始)的结尾。
id就是单词在words列表中的顺序。 我们在dfs的时候,成功搜索到单词的标志就是最终的指针指向的字母的id不为-1(构造函数中对id初始赋值为-1)。

(2)我们可能会在char数组中多次搜索到同样的单词,所以需要额外用一个哈希表unordered_set来对dfs函数搜索到的单词的id进行记录。

(3)我们把二维char数组board记录到一个全局二维char变量g中,并用全局变量rows和cols记录二维数组的行数和列数,这样dfs函数可以少传几个参数。

代码如下:

class Solution {
public:
    vector<vector<char>> g;                  //记录二维字符数组
    int rows, cols, dx[4] = {0, 1, -1, 0}, dy[4] = {1, 0, 0, -1};          //g的行数、列数、dfs的四个方向
    unordered_set<int> ids;                   //用来去重

    struct Node {                                 //Trie树的节点
        int id;
        Node *son[26];
        Node() {
            id = -1;                              
            for(int i = 0; i < 26; ++i) {
                son[i] = NULL;
            }
        }
    }*root;

    void insert(string &word, int id) {                  //在Trie树中插入单词,并在结尾记录id
        Node *p = root;
        for(int i = 0; i < word.size(); ++i) {
            int u = word[i] - 'a';
            if(p -> son[u] == NULL) {
                p -> son[u] = new Node();
            }
            p = p -> son[u];
        }
        p -> id = id;
    }

    void dfs(int x, int y, Node *p) {
        if(p -> id != -1) {                        //搜索成功一个单词,在哈希表ids中记录id
            ids.insert(p -> id);
        }
        char temp = g[x][y];                        //题目要求同一个单元格内的字母在一个单词中不允许被重复使用,所以我们先记录下这个位置的字符,然后暂时将他修改为'.',表示不可用,dfs之后再恢复现场
        g[x][y] = '.';
        for(int i = 0; i < 4; ++i) {
            int newX = x + dx[i], newY = y + dy[i];
            if(newX >= 0 && newX < rows && newY >= 0 && newY < cols                  //如果下一个位置不越界
            && g[newX][newY] != '.' && p -> son[g[newX][newY] - 'a'] != NULL) {      //且下一个位置未被使用过(g[newX][newY] != '.'),且在Trie树中下一个字母是当前字母的孩子
                dfs(newX, newY, p -> son[g[newX][newY] - 'a']);                      //继续搜索
            }
        }
        g[x][y] = temp;                              //恢复现场,把这个位置的字母从'.'恢复到它本来的样子
    }
    vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {
        vector<string> res;
        g = board;
        rows = g.size(), cols = g[0].size();
        root = new Node();
        for(int i = 0; i < words.size(); ++i) {                  //把所有单词插入到一个Trie树中
            insert(words[i], i);
        }
        for(int i = 0; i < rows; ++i) {
            for(int j = 0; j < cols; ++j) {
                int u = g[i][j] - 'a';                         
                if(root -> son[u] != NULL) {                   //如果当前字母是Trie树根节点的孩子,说明从当前位置搜索单词可能有戏
                    dfs(i, j, root -> son[u]);                  //开始搜索
                }
            }
        }
        for(auto id : ids) {                                   //去重之后,我们就知道哪些单词搜索到了,把这些搜索到的单词加入到结果数组res中
            res.push_back(words[id]);
        }
        return res;
    }
};
posted @ 2020-08-14 15:14  machine_gun_lin  阅读(156)  评论(0编辑  收藏  举报