【回溯-6】79.单词搜索

题目描述:

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。

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

示例 1:

复制代码
输入:board = [['A','B','C','E'],['S','F','C','S'],['A','D','E','E']], word = "ABCCED"
输出:true

示例 2:

复制代码
输入:board = [['A','B','C','E'],['S','F','C','S'],['A','D','E','E']], word = "SEE"
输出:true

示例 3:

复制代码
输入:board = [['A','B','C','E'],['S','F','C','S'],['A','D','E','E']], word = "ABCB"
输出:false

解题思路:

方法一:回溯 + DFS

核心思路:

从每个格子出发,尝试匹配 word 的第一个字符,然后向四个方向扩展。

  • 匹配成功:继续匹配下一个字符

  • 匹配失败:回溯,换方向

  • 越界或已访问:跳过

关键操作:

  1. 标记已访问 :把当前格子改成特殊字符(如 '#'),避免重复使用

  2. 四个方向:上、下、左、右

  3. 回溯:恢复格子的原字符

具体过程示例:

board = [['A','B','C','E'],['S','F','C','S'],['A','D','E','E']], word = "ABCCED"

复制代码
路径: A → B → C → C → E → D
(0,0) → (0,1) → (0,2) → (1,2) → (2,2) → (2,1)

A B C E
S F C S
A D E E

代码实现:

cpp 复制代码
class Solution {
public:
    bool exist(vector<vector<char>>& board, string word) {
        int m = board.size(), n = board[0].size();
        
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (dfs(board, word, i, j, 0)) {
                    return true;
                }
            }
        }
        
        return false;
    }
    
private:
    bool dfs(vector<vector<char>>& board, string& word, int i, int j, int index) {
        // 匹配完成
        if (index == word.size()) return true;
        
        // 越界或不匹配
        if (i < 0 || i >= board.size() || j < 0 || j >= board[0].size()
            || board[i][j] != word[index]) {
            return false;
        }
        
        // 标记已访问
        char temp = board[i][j];
        board[i][j] = '#';
        
        // 四个方向搜索
        bool found = dfs(board, word, i + 1, j, index + 1)
                  || dfs(board, word, i - 1, j, index + 1)
                  || dfs(board, word, i, j + 1, index + 1)
                  || dfs(board, word, i, j - 1, index + 1);
        
        // 恢复
        board[i][j] = temp;
        
        return found;
    }
};

复杂度分析:

设 m × n 是网格大小,L 是单词长度。

维度 复杂度 说明
时间复杂度 O(m × n × 3^L) 每个格子出发,每步最多3个方向(不回头)
空间复杂度 O(L) 递归栈深度

为什么是 3^L 而不是 4^L?

因为每次不能走回头路(已访问的格子被标记),所以每步最多3个方向。

关键细节:

1. 为什么用 '#' 标记已访问?
  • 避免重复使用同一个格子

  • 比额外维护 visited 数组更省空间

  • 回溯时恢复原字符

2. 为什么四个方向用 || 连接?

只要有一个方向能找到,就返回 true。|| 有短路特性,找到后不再继续搜索。

3. 为什么先判断 index == word.size()?

当 index 等于单词长度时,说明所有字符都匹配完了,返回 true。

4. 为什么越界判断放在前面?

避免访问越界的内存,同时判断字符是否匹配。

方法二:用 visited 数组

代码实现:

cpp 复制代码
class Solution {
public:
    bool exist(vector<vector<char>>& board, string word) {
        int m = board.size(), n = board[0].size();
        vector<vector<bool>> visited(m, vector<bool>(n, false));
        
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (dfs(board, word, visited, i, j, 0)) {
                    return true;
                }
            }
        }
        
        return false;
    }
    
private:
    bool dfs(vector<vector<char>>& board, string& word,
             vector<vector<bool>>& visited, int i, int j, int index) {
        if (index == word.size()) return true;
        
        if (i < 0 || i >= board.size() || j < 0 || j >= board[0].size()
            || visited[i][j] || board[i][j] != word[index]) {
            return false;
        }
        
        visited[i][j] = true;
        
        bool found = dfs(board, word, visited, i + 1, j, index + 1)
                  || dfs(board, word, visited, i - 1, j, index + 1)
                  || dfs(board, word, visited, i, j + 1, index + 1)
                  || dfs(board, word, visited, i, j - 1, index + 1);
        
        visited[i][j] = false;
        
        return found;
    }
};

缺点:需要额外 O(m × n) 空间。

两种方法对比:

方法 时间复杂度 空间复杂度 推荐度
原地标记 O(m × n × 3^L) O(L) ⭐⭐⭐⭐⭐
visited 数组 O(m × n × 3^L) O(m × n + L) ⭐⭐⭐⭐

总结:

要点 说明
核心思想 从每个格子出发,DFS 匹配单词
关键操作 标记已访问 → 四方向搜索 → 恢复
终止条件 index == word.size() 返回 true
时间复杂度 O(m × n × 3^L)
空间复杂度 O(L)
相关推荐
一去不复返的通信er1 小时前
力扣-图论-岛屿数量
算法·leetcode·图论
青瓦梦滋2 小时前
WebSocket协议
linux·服务器·网络·c++·websocket·网络协议·http
程序 代码狂人4 小时前
切片表与分区表的区别(数据开发面试版)
java·大数据·开发语言·数据仓库·算法
HDU-Dade5 小时前
【校招笔试真题】简要介绍 Self-Attention 的前沿算法
人工智能·算法
Xin7706 小时前
LeetCode 79. 单词搜索(DFS 回溯法)
leetcode
小郎君6587 小时前
Vue keep-alive 切标签后滚动位置回顶?因为 onDeactivated 里读到的 scrollTop 是 0
算法
CodeYp20197 小时前
第15讲:二叉搜索树
开发语言·c++·二叉搜索树
邵奈一7 小时前
GPT-6.1 Terra速度是Sol的3倍,价格减半
算法
RaaS1007 小时前
Node.js Brotli RCE 漏洞自查指南:fetch、undici、axios 都在影响面,三分钟确认你的版本
人工智能·深度学习·算法