LeetCode OJ 之 Word Search II (单词搜索 - 二)

这篇博客讨论了LeetCode中的Word Search II问题,通过构建Trie树来优化单词搜索算法,避免超时。博主详细解释了如何利用Trie树进行深度优先搜索,检查路径是否为单词的前缀,并在不是任何单词前缀时停止回溯,从而提高效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:

Given a 2D board and a list of words from the dictionary, find all words in the board.

Each word must be constructed from letters of sequentially adjacent cell, where "adjacent" cells are those horizontally or vertically neighboring. The same letter cell may not be used more than once in a word.

For example,
Given words = ["oath","pea","eat","rain"] and board =

[
  ['o','a','a','n'],
  ['e','t','a','e'],
  ['i','h','k','r'],
  ['i','f','l','v']
]
Return  ["eat","oath"] .

Note:
You may assume that all inputs are consist of lowercase letters a-z.

click to show hint.

You would need to optimize your backtracking to pass the larger test. Could you stop backtracking earlier?

If the current candidate does not exist in all words' prefix, you could stop backtracking immediately. What kind of data structure could answer such query efficiently? Does a hash table work? Why or why not? How about a Trie? If you would like to learn how to implement a basic trie, please work on this problem: Implement Trie (Prefix Tree) first.

思路:

参考word search:http://blog.youkuaiyun.com/u012243115/article/details/42075915 。

如果直接采用word search的方法会超时,需要采用更加高效的查找方法。根据提示可以采用Trie树。

Trie树参考:http://blog.youkuaiyun.com/u012243115/article/details/47252213 。

即先构造一棵前缀树,然后对二维板的每个字符进行深搜。判断某个路径是否是某个单词。如果当前路径不是任何单词的前缀则立即返回。如果是,则继续递归。

代码:

class TrieNode 
{
public:
    // Initialize your data structure here.
    bool isWord;
    TrieNode *next[26];
    TrieNode() 
    {
        isWord = false;
        for(int i = 0 ; i < 26 ; i++)
            next[i] = NULL;
    }
};

class Trie {
public:
    Trie() 
    {
        root = new TrieNode();
    }

    // Inserts a word into the trie.
    void insert(string word) 
    {
        int len = word.size();
        TrieNode *p = root;
        for(int i = 0 ; i < len ; i++)
        {
            //如果不存在,则建立新结点。如果存在,则继续向下
            if(p->next[word[i] - 'a'] == NULL)
                p->next[word[i] - 'a'] = new TrieNode();
            p = p->next[word[i] - 'a'];
        }
        p->isWord = true;   //把最后一个结点的isWord置为true,表示从根结点到当前结点保存了一个单词
    }

    // Returns if the word is in the trie.
    bool search(string word) 
    {
        TrieNode *p = find(word);
        return p != NULL && p->isWord;  //还要判断当前结点的isWord是否为真,为真才表示是个单词。如果为假,则表示word只是已存在单词的前缀
    }

    // Returns if there is any word in the trie
    // that starts with the given prefix.
    bool startsWith(string prefix) 
    {
        TrieNode *p = find(prefix);
        return p != NULL;   //和上面一个函数比较,这里由于查找前缀,因此不需要判断isWord
    }
    
    //查找key是否存在
    TrieNode *find(string key)
    {
        int len = key.size();
        TrieNode *p = root;
        for(int i = 0 ; i < len && p != NULL ; i++)
        {
            p = p->next[key[i] - 'a'];
        }
        return p;
    }
private:
    TrieNode* root;     //注意:根结点并不表示字符
};


class Solution {
public:
    void dfs(Trie &trie , vector<vector<char> > &board, string word, size_t i , size_t j, vector<vector<bool> > &visited , vector<string> &result)
    {
        if(i<0 || j<0 || i>=board.size() || j>= board[0].size())
            return ;
        //如果当前字母被访问过
        if(visited[i][j])
            return ;
        word += board[i][j];
        
        //这一步很重要,如果word不是某个单词的前缀,则提前返回(没有这一步结果超时了)
        if(!trie.startsWith(word))
            return ;
        if(trie.search(word))
        {
            if(find(result.begin() , result.end() , word) == result.end())  //注意:可能board中重复找到已经找到的word,比如board={"aa"},word={"a"},因此需要判断result里是否已经找到。如果没找到,才加入到result
                result.push_back(word);
        }
        
        //以上都不满足,说明当前字母符合条件,置为true,继续对当前单词的上下左右深搜
        visited[i][j] = true;
        if(i - 1 >= 0)
            dfs(trie , board , word , i-1 , j , visited , result) ;
        if(i + 1 < board.size())
            dfs(trie , board , word , i+1 , j , visited , result) ;
        if(j - 1 >= 0)
            dfs(trie , board , word , i , j-1 , visited , result) ;
        if(j + 1 < board[0].size())
            dfs(trie , board , word , i , j+1 , visited , result) ;
        //从board[i][j]点开始深搜完成后,visited要重置为false
        visited[i][j] = false;
    }
    vector<string> findWords(vector<vector<char>>& board, vector<string>& words) 
    {
        Trie trie;
        vector<string> result;
        size_t len = words.size();
        
        //先对所有单词构造一个trie树
        for(int i = 0 ; i < len ; i++)
        {  
            trie.insert(words[i]);
        }
        size_t row = board.size();
        if(row == 0)
            return result;
        size_t col = board[0].size();
        vector<vector<bool> > visited(row , vector<bool>(col , false)); //对已经访问过的字符标记
        
        //然后对二维板上的每个字符进行深搜,使用trie树减少深搜时间
        for(int i = 0 ; i < row ; i++)
        {
            for(int j = 0 ; j < col ; j++)
            {
                dfs(trie , board , "" , i , j , visited , result);
            }
        }
        return result;
    }
};


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值