XingHuiSamaの宝藏之地
首页项目归档照片墙音乐说说杂谈友链关于
封面

Leetcode一百题——单词搜索

2026-05-12 16:25:24
# Leetcode
# C++
# 题解
# 工作

给定一个 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

‍

提示:

  • m == board.length
  • n = board[i].length
  • 1 <= m, n <= 6
  • 1 <= word.length <= 15
  • board 和 word 仅由大小写英文字母组成

‍

1. 算法核心思想

本题是一道典型的二维矩阵回溯(Backtracking)题目。其核心在于:以矩阵中的每一个点作为潜在起点,尝试向四个方向“嗅探”目标单词的路径。

解决本题的关键在于“状态保护与还原”:

  • 深度优先搜索 (DFS):从一个点出发,只要字符匹配,就继续往深处走。
  • 回溯 (Backtracking):当我们走入死胡同(四个方向都走不通)时,必须把当前格子的“已访问”标记清除,退回到上一步。如果不清除标记,当你从另一条路径重新绕回这个格子时,它会被错误地认为“已走过”,导致漏解。

2. 代码逻辑拆解

  • 第一阶段:全图扫描寻起点 在 exist 函数中,通过双重循环遍历整个 m x n 的网格。一旦发现字符与 word[0] 匹配,立即开启 func 进行深度搜索。只要任意一个起点能走通,整体就返回 true。

  • 第二阶段:防御性剪枝与匹配判定 在 func 入口处进行三重判定:

    1. if (ans):如果已经在其他平行宇宙找到了答案,当前分支立刻停止。
    2. if (board[x][y] != word[k]):当前格子字符不对,此路不通。
    3. if (k == word.size() - 1):能走到这一步且字符匹配,说明整条路径已打通,标记 ans = true。
  • 第三阶段:标记与扩散

    • 标记:进入递归前,将 visited[x][y] 置为 1。
    • 扩散:分别判断上下左右四个方向。只有在坐标合法且未被访问过的情况下,才递进到下一层(k + 1)。
  • 第四阶段:撤销选择(回溯灵魂) 这是最关键的一行:visited[x][y] = 0;。当当前的四个方向都探索完毕(不论成功失败),我们必须把这个格子的标记抹去。这保证了在后续的搜索路径中,该格子依然是“可用”的。

class Solution {
public:
    bool ans = false;

    bool exist(vector<vector<char>>& board, string word) {
        int m = board.size();
        int n = board[0].size();
        vector<vector<int>> visited(m, vector<int>(n, 0));

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                // 对每一个匹配首字母的格子都发起搜索
                if (board[i][j] == word[0]) {
                    func(board, visited, word, 0, i, j);
                    if (ans) return true;
                }
            }
        }
        return false;
    }

    void func(vector<vector<char>>& board, vector<vector<int>> &visited, string& word, int k, int x, int y) {
        if (ans) return;
        
        // 如果当前格子字符不匹配,直接返回
        if (board[x][y] != word[k]) return;

        // 如果匹配且是最后一个字母
        if (k == word.size() - 1) {
            ans = true;
            return;
        }

        // 做选择:标记已访问
        visited[x][y] = 1;

        // 方向数组(上下左右),比写四个 if 更简洁
        int dx[4] = {-1, 1, 0, 0};
        int dy[4] = {0, 0, -1, 1};

        for (int i = 0; i < 4; i++) {
            int nextX = x + dx[i];
            int nextY = y + dy[i];

            // 越界检查和访问检查
            if (nextX >= 0 && nextX < board.size() && nextY >= 0 && nextY < board[0].size()) {
                if (visited[nextX][nextY] == 0) {
                    func(board, visited, word, k + 1, nextX, nextY);
                }
            }
        }

        // 撤销选择:回溯还原状态(非常重要!)
        visited[x][y] = 0;
    }
};

3. 复杂度分析

  • 时间复杂度:****O(N⋅3L)O(N \cdot 3^L)O(N⋅3L)

    其中 NNN 是网格中的格子总数,LLL 是字符串 word 的长度。对于每一个起点,我们有 3 个方向可以探索(除去来的那个方向)。最坏情况下,我们要从每个格子出发都搜一遍。

  • 空间复杂度:****O(N)O(N)O(N)

    主要开销来自于 visited 标记数组。此外,递归调用的栈深度最大为 LLL。

‍

avatar

XingHuiSama

在代码、学术与分子动力学模拟间穿梭的普通人。近期正埋头于 GROMACS 模拟研究与神经网络计算。

RECOMMENDED

GROMACS 2025 分子动力学模拟初探2222

2026-03-24 07:00:45

Computational Chemistry Tool 工具二介绍

2026-04-01 07:00:23

Leetcode一百题——字母异位词分组

2026-04-07 15:34:18

Table of Contents