leetcode
leetcode 51 ~ 100
单词搜索

单词搜索

难度:

标签:

题目描述

给定一个 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
  • boardword 仅由大小写英文字母组成

 

进阶:你可以使用搜索剪枝的技术来优化解决方案,使其在 board 更大的情况下可以更快解决问题?

代码结果

运行时间: 204 ms, 内存: 17.1 MB


/*
题目思路:
1. 使用DFS(深度优先搜索)和Java Stream API来处理二维网格和字符串的匹配问题。
2. 遍历二维网格的每个单元格,使用Stream和filter方法筛选出符合条件的起始点,进行递归搜索。
3. 使用Streams的任何操作都必须避免副作用,标记和回溯需要考虑副作用问题。
*/
 
import java.util.stream.IntStream;
 
public class Solution {
    public boolean exist(char[][] board, String word) {
        return IntStream.range(0, board.length)
            .boxed()
            .flatMap(i -> IntStream.range(0, board[0].length)
                    .filter(j -> dfs(board, word, i, j, 0))
                    .boxed())
            .findFirst()
            .isPresent();
    }
 
    private boolean dfs(char[][] board, String word, int i, int j, int index) {
        if (index == word.length()) return true;
        if (i < 0 || j < 0 || i >= board.length || j >= board[0].length || board[i][j] != word.charAt(index))
            return false;
        char temp = board[i][j];
        board[i][j] = ' ';
        boolean 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;
    }
}

解释

方法:

这个题解使用回溯法来解决单词搜索问题。从二维网格的每个位置开始,如果该位置的字符与目标单词的第一个字符匹配,就开始回溯搜索。在回溯搜索过程中,选择当前位置的上、下、左、右四个相邻位置作为下一步搜索的选择。对于每个选择,首先判断是否满足有效性条件(未越界、未访问过、字符匹配),如果满足则标记为已访问,将字符加入路径并递归搜索下一层。如果搜索到目标单词的所有字符,则返回 True。如果所有选择都搜索完毕仍未找到目标单词,则回溯到上一层,撤销当前选择的访问标记和路径。如果从所有起始位置的搜索都无法找到目标单词,则返回 False。

时间复杂度:

O(m*n*4^L)

空间复杂度:

O(L)

代码细节讲解

🦆
在回溯过程中,如果当前路径长度与目标单词长度相等,为什么可以直接返回True,而不再检查路径中的字符是否完全匹配目标单词?
在此算法中,每次添加字符到路径中前,已经确保该字符与目标单词中对应位置的字符匹配。因此,当路径长度与目标单词长度相等时,可以确信路径中的字符序列与目标单词完全匹配,无需再次检查。
🦆
为什么在每一步回溯中,选择的顺序是上、下、左、右,改变这个顺序会对算法的效率产生影响吗?
选择的顺序(上、下、左、右)基本上是任意的,主要是为了系统地探索所有可能的相邻位置。改变这个顺序通常不会影响算法的最终结果,但在特定的输入情况下,可能会影响到搜索的效率,例如某个顺序可能稍早找到解决方案,从而减少一些不必要的递归调用。
🦆
在递归函数中,为什么要使用path和visited两个结构,而不是仅用visited来记录访问过的位置和路径信息?
在此算法中,使用`path`来存储当前路径中的字符,主要用于比较路径长度和目标单词长度,而`visited`用于记录已经访问过的格子,防止在同一路径中重复访问。虽然理论上可以仅用`visited`来跟踪访问状态,但使用`path`可以更直观地管理当前路径的字符,并简化代码逻辑。
🦆
对于边界条件的处理,如何确保在递归过程中不会访问到二维网格之外的元素?
在递归函数中,每次递归调用前都会检查当前坐标`(x, y)`是否越界,即检查`x`和`y`是否在合法的范围内(`0 <= x < m` 和 `0 <= y < n`)。这样可以确保所有递归访问都限制在网格的边界内,避免访问非法的内存地址或引发错误。

相关问题

单词搜索 II

给定一个 m x n 二维字符网格 board 和一个单词(字符串)列表 words, 返回所有二维网格上的单词 。

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

 

示例 1:

输入:board = [["o","a","a","n"],["e","t","a","e"],["i","h","k","r"],["i","f","l","v"]], words = ["oath","pea","eat","rain"]
输出:["eat","oath"]

示例 2:

输入:board = [["a","b"],["c","d"]], words = ["abcb"]
输出:[]

 

提示:

  • m == board.length
  • n == board[i].length
  • 1 <= m, n <= 12
  • board[i][j] 是一个小写英文字母
  • 1 <= words.length <= 3 * 104
  • 1 <= words[i].length <= 10
  • words[i] 由小写英文字母组成
  • words 中的所有字符串互不相同