212 单词搜索II

思路:

先将单词插入到前缀树中,然后再DFS一步步去判断下一个要遍历的字符是否存在前缀树中,若存在,则加入中间变量中,若当前遍历的字符序列在字典树中组成一个单词,则加入ans中

不存在,则停止该方向的搜索,因为前缀不存在,则后面DFS生成的单词均以此为前缀,均不存在

在DFS时使用一个set来保存存在于前缀树中的单词,因为遍历时可能会有重复的单词加入,注意DFS时对于vis数组和中间变量的重置。

最后将自动排序好的set转为vector作为ans返回即可。

class Trie
{
public:
    bool is_end;
    Trie *next[26]; // 单词在a - z之间
    Trie()
    {
        is_end = false;
        for(int i = 0; i < 26; ++i) next[i] = nullptr;
    }

    void insert(const string& word)
    {
        Trie *t = this;
        for(auto i : word)
        {
            if(t->next[i - 'a'] == nullptr) t->next[i - 'a'] = new Trie();
            t = t->next[i - 'a'];
        }
        t->is_end = true;
    }
};

class Solution {
    Trie *root;
    int row, col;
    int dx[4] = {0, -1, 0, 1}, dy[4] = {1, 0, -1, 0};
    set<string> aws; // 暂存遍历时的存在的单词,可能会有重复元素,去重用set
    string temp; // 用来存储遍历时生成的中间字符串
    vector<vector<bool>> vis; // 标记是否访问过
public:
    Solution(){root = new Trie();}

    void DFS(int i, int j, const vector<vector<char>>& board, const Trie *t)
    {
        char c = board[i][j];
        printf("%c", c);
        temp.push_back(c);
        if(t->next[c - 'a'] == nullptr)
        {
            temp.pop_back();
            // 每次生成字符串时不能访问这次已经访问过的点,但是下一次开始时能访问与上一次重复的点,要重置vis
            vis[i][j] = false;
            return;
        }
        vis[i][j] = true;
        if(t->next[c - 'a']->is_end == true) aws.insert(temp); // 组成前缀树中的一个单词
        for(int k = 0; k < 4; ++k)
        {
            int I = dx[k] + i, J = dy[k] + j;
            if(I >= 0 && I < row && J >= 0 && J < col && vis[I][J] == false)
                DFS(I, J, board, t->next[c - 'a']);
        }
        // 运行到这里重置vis和temp
        vis[i][j] = false;
        temp.pop_back();
    }

    vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {
        for(auto s : words) root->insert(s); // 先将单词插入到前缀树中
        row = board.size(), col = board[0].size();
        vis.resize(row);
        for(int i = 0; i < row; ++i)
        {
            vis[i].resize(col);
            fill(vis[i].begin(), vis[i].end(), false);
        }
        for(int i = 0; i < row; ++i) // 对整个图DFS
            for(int j = 0; j < col; ++j)
                DFS(i, j, board, root);
        vector<string> ans(aws.begin(), aws.end()); // set转换为vector
        return ans;
    }
};
<think>好的,我现在需要解决LeetCode 212单词搜索II的问题。用户询问了解题思路和示例代码,所以我得先回忆一下这个题目的要求和相关解法。 首先,题目是给定一个二维字符网格和一个单词列表,要求找出所有同时在网格和列表中出现的单词单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”指的是水平或垂直相邻的单元格,同一个单元格内的字母在一个单词中不允许被重复使用。比如输入一个board和words列表,输出所有能在board中找到的words中的单词。 那么,常见的解法是什么呢?记得之前学过的,对于这种多个单词搜索问题,直接对每个单词单独使用回溯法(DFS)可能会超时,因为如果words列表很大,每个单词都遍历整个board的话,时间复杂度会很高。例如,假设words有k个单词,每个单词长度是L,board是m*n的,那么时间复杂度大概是O(k * L * m * n * 4^L),这显然在单词数量多的时候效率很低。 这时候想到使用Trie树(字典树)来优化。Trie树可以将所有待查找的单词构建成树结构,这样在DFS过程中,可以同时沿着Trie树的路径进行搜索,一旦发现当前路径不在Trie树中,就可以提前剪枝,减少不必要的搜索。这样可以有效减少重复的搜索路径,提高效率。 接下来,具体步骤应该包括: 1. 构建Trie树:将所有words中的单词插入到Trie结构中,每个节点保存子节点和是否为单词结尾的标志。 2. DFS遍历board:对每个单元格作为起点,进行深度优先搜索,同时在Trie树中检查当前路径是否存在。如果存在,继续搜索;如果不存在,剪枝。当遇到Trie树中的单词节点时,将该单词加入结果集,并避免重复添加(比如可以标记该节点为非单词,或者在加入后删除)。 3. 处理重复和剪枝:需要注意在找到单词后,及时从Trie树中移除,或者在节点中添加标记,防止同一单词多次被加入结果。同时,DFS过程中需要记录已访问的单元格,避免重复使用。 然后,具体实现时,需要考虑Trie的结构。比如,每个节点可能有26个子节点(对应26个字母),或者使用哈希表存储子节点。另外,DFS的递归终止条件应该是当前位置超出board边界,或者当前字符不在Trie树的下一个节点中,或者该单元格已经被访问过。 举个例子,假设board如下: [ ['o','a','a','n'], ['e','t','a','e'], ['i','h','k','r'], ['i','f','l','v'] ] words = ["oath","pea","eat","rain"] 构建Trie树后,从每个单元格开始DFS。比如,从(0,0)的'o'开始,检查Trie中是否有以'o'开头的单词。然后向四个方向搜索,寻找下一个字符。当组成一个完整的单词时,如"oath",就将其加入结果。 此外,剪枝的关键在于及时判断当前路径是否存在于Trie中。如果当前路径已经不在Trie中,就没必要继续搜索下去。例如,如果当前路径是'oa',而Trie中不存在以'oa'为前缀的单词,就可以停止继续搜索。 接下来,代码实现的大致步骤: 1. 定义TrieNode结构,包含children和is_word字段。 2. 构建Trie树,将所有单词插入。 3. 遍历board的每个单元格,作为DFS的起点。 4. 在DFS过程中,维护当前路径形成的字符串,同时根据Trie树的节点进行判断。如果当前字符对应的子节点存在,继续搜索;如果该子节点标记为单词,则将其加入结果,并取消标记(避免重复)。 5. 使用visited矩阵或者修改board临时标记为已访问,回溯时恢复。 需要注意的问题: - 如何避免同一单词多次被添加?比如,可能在board中存在多条路径形成同一个单词。题目要求返回所有出现在words中的单词,但不重复。所以需要在找到单词后,将其从Trie中删除,或者标记为已找到。 - 优化空间:Trie的构建方式,是否可以用更节省空间的结构,比如用字典代替固定大小的数组。 - 处理特殊字符?题目中说明是二维字符网格,所以都是小写字母。 现在,参考给出的引用内容。引用3提到,当深搜路径不存在于字典树时,就停止搜索。这点对应了Trie的剪枝。而代码部分可能涉及到Trie的构建和DFS的结合。 另外,引用1、2、4都是题目描述,所以关键解法在引用3中提到的使用字典树优化DFS。 综上,解题思路是使用Trie树优化DFS,减少不必要的搜索,从而提高效率。具体实现步骤包括构建Trie,遍历每个单元格进行DFS,剪枝条件判断,以及处理结果的去重。</think>### LeetCode 212 单词搜索 II 解题思路 #### 核心思路:Trie树 + DFS回溯 1. **构建Trie树** 将所有待查找单词存入字典树,便于快速判断当前路径是否为某个单词的前缀或完整单词。Trie节点结构如下: - `children`: 子节点(数组或哈希表) - `is_end`: 标记是否为单词结尾 2. **DFS回溯搜索** 遍历网格中的每个单元格作为起点,通过DFS尝试所有可能的路径: - 若当前路径不在Trie树中,直接剪枝。 - 若路径形成完整单词,加入结果集并更新Trie节点(避免重复记录)。 3. **剪枝优化** - 移除已找到的单词(例如修改`is_end`为`False`)[^3]。 - 使用临时标记避免重复访问单元格。 #### 示例代码 ```python class TrieNode: def __init__(self): self.children = {} self.is_end = False class Solution: def findWords(self, board: List[List[str]], words: List[str]) -> List[str]: # 构建Trie树 root = TrieNode() for word in words: node = root for char in word: if char not in node.children: node.children[char] = TrieNode() node = node.children[char] node.is_end = True result = [] m, n = len(board), len(board[0]) def dfs(i, j, node, path): char = board[i][j] if char not in node.children: return current_node = node.children[char] # 检查是否找到单词 if current_node.is_end: result.append(path + char) current_node.is_end = False # 避免重复 # 临时标记已访问 board[i][j] = '#' for dx, dy in [(-1,0),(1,0),(0,-1),(0,1)]: x, y = i + dx, j + dy if 0 <= x < m and 0 <= y < n and board[x][y] != '#': dfs(x, y, current_node, path + char) board[i][j] = char # 回溯 # 遍历所有起点 for i in range(m): for j in range(n): dfs(i, j, root, "") return result ``` #### 代码说明 1. **Trie树构建**:将所有单词插入字典树,标记每个单词的结束节点。 2. **DFS函数**:从`(i,j)`出发,沿四个方向搜索: - 若字符不在Trie树中则终止。 - 若找到完整单词,加入结果并标记为已找到。 - 临时修改`board[i][j]`为`#`防止重复访问,回溯时恢复。 3. **时间复杂度**:$O(m \times n \times 4^L)$,其中$L$为最长单词长度,但通过Trie剪枝大幅减少实际计算量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值