首页 简历|笔试面试
您现在的位置:百分网 > 职场 > 笔试面试 > LeetCode

79.单词搜索

  • 25年9月4日 发布
  • 174.24KB 共9页
79.单词搜索79.单词搜索79.单词搜索79.单词搜索79.单词搜索

79. 单词搜索

079.单词搜索

本题解同时收录在星球专栏《二哥的 LeetCode 刷题笔记》中,专栏托管在语雀平台上

(更方便你沉浸式阅读和做笔记),访问地址和密码:https://t.zsxq.com/6iuzn6I

题意

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格

中,返回 true ;否则,返回 false 。

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平

相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

难度

中等

示例

示例 1:

079.%E5%8D%95%E8%AF%8D%E6%90%9C%E7%B4%A2-20230921135734.png

输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word =

"ABCCED"

输出:true

示例 2:

079.%E5%8D%95%E8%AF%8D%E6%90%9C%E7%B4%A2-20230921135755.png

输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "SEE"

输出:true

示例 3:

079.%E5%8D%95%E8%AF%8D%E6%90%9C%E7%B4%A2-20230921135815.png

输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCB"

输出:false

分析 1

本题我们要用到的算法是——深度优先搜索+回溯算法。

首先,在 board 中找到 word 的第一个字符,然后再往其他方向查找有没有第二个字符,

但是这么做的话,第三步有可能回到原点(第二个字符折回到第一个字符),从而陷入无

限递归的死局。

所以我们要引入一个布尔型的二维数组 vis[][],来标记哪些点已经走过的,走过的就不能

再次选择。

当四个方向都没有可行的策略时,我们就要用到——回溯算法,上一题中我们其实有聊

过。

回溯算法其实也好理解,毕竟一条路走到黑,走不出去的时候,最好换条路走。

来看题解。

class Solution {

// 定义四个方向,即上下左右

int[] dirX = {0, 0, 1, -1};

int[] dirY = {1, -1, 0, 0};

// 用于记录某个位置是否已经访问过

boolean[][] vis;

// 二维字符数组,即题目给定的 board

char[][] map;

// 要查找的单词

String str;

public boolean exist(char[][] board, String word) {

vis = new boolean[board.length][board[0].length];

map = board;

str = word;

// 遍历整个 board,查找起点

for(int i = 0;i < board.length;i++){

for(int j = 0;j < board[0].length;j++){

// 如果找到了单词的第一个字符,那么从此位置开始深度优先搜索

if(board[i][j] == word.charAt(0)){

if(DFS(i, j, 0))

return true; // 如果找到了单词,则返回 true

}

}

}

// 所有位置都尝试过但没找到,返回 false

return false;

}

// 判断移动是否合法

private boolean checkMove(int posX,int posY){

return posX < map.length && posX >= 0 && posY < map[0].length && posY

>= 0 && !vis[posX][posY];

}

// 深度优先搜索函数

private boolean DFS(int posX,int posY,int step){

// 当前位置字符与单词不匹配,返回 false

if(map[posX][posY] != str.charAt(step))

return false;

// 如果匹配到单词的最后一个字符,返回 true

if(step == str.length() - 1)

return true;

boolean res = false;

vis[posX][posY] = true; // 标记当前位置已访问

// 遍历四个方向

for(int i = 0;i < 4 && !res;i++){

int nextX = posX + dirX[i];

int nextY = posY + dirY[i];

// 如果移动合法并且下一个位置匹配,继续深度优先搜索

if(checkMove(nextX,nextY) && DFS(nextX,nextY,step + 1))

res = true;

}

// 回溯,即撤销当前位置的访问标记,以便后续的搜索

vis[posX][posY] = false;

return res;

}

}

这段代码在一个二维字符数组(即 board)中查找一个特定的单词 word 是否存在。搜索

的方式是从一个字符开始,然后向上下左右四个方向移动。这是一个典型的深度优先搜

索,并且涉及到回溯,即当当前路径不可能匹配整个单词时,需要退回到上一步并尝试其

他可能的路径。

简单的图解:

1. 假设 board 是这样的:

ABC

DEF

GHI

2. 要查找的单词是:“BEHI”。

3. 搜索流程:

从“B”开始(即第一行第二列),然后向四个方向搜索:

A [B] C --> A [B] [C]

D E F D E F

G H I G H I

从“C”开始,无法匹配“BEHI”,所以回退到“B”,然后尝试其他方向。

A [B] C --> A [B] C

D [E] F D [E] F

G H I G H I

从“E”开始,接下来的字母是“H”,所以向下搜索。

A [B] C --> A [B] C

D [E] F D [E] F

G [H] I G [H] I

接下来的字母是“I”,所以再次向下搜索。

A [B] C --> A [B] C

D [E] F D [E] F

G [H] [I] G [H] [I]

这样,就找到了整个单词“BEHI”。

079.%E5%8D%95%E8%AF%8D%E6%90%9C%E7%B4%A2-20230921141736.png

分析 2

LeetCode 的原题中有提示我们使用剪枝,我们来试一下,效率会不会更高。

剪枝算法是在搜索算法中用于减少无用或冗余搜索路径的策略。通过排除某些选择,可以

提高搜索效率。

将前面的回溯策略加上剪枝优化的思路如下:

1. 早停策略:如果当前已经匹配的字符数量与目标单词的长度相同,则可以提前终止搜

索,因为我们已经找到一个匹配的路径。

2. 字符不匹配:如果 board[x][y]与目标单词中的当前字符不匹配,则立即返回 false,

无需进一步搜索。

3. 避免重复访问:为了防止在搜索过程中重复访问同一个格子,我们可以用一个标记数

组 visited 来记录哪些格子已经被访问过。当我们从一个格子返回时,需要将其标记

为未访问。

下面是带有剪枝策略的题解:

class Solution {

private int[][] directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};

private int m, n;

public boolean exist(char[][] board, String word) {

m = board.length;

n = board[0].length;

boolean[][] visited = new boolean[m][n];

// 遍历每一个起始点,尝试从这个点开始搜索

for (int i = 0; i < m; i++) {

for (int j = 0; j < n; j++) {

if (dfs(board, word, 0, i, j, visited)) {

return true;

}

}

}

return false;

}

private boolean dfs(char[][] board, String word, int index, int x, int y, boolean[]

[] visited) {

// 剪枝:当前位置的字符与目标字符不匹配

if (board[x][y] != word.charAt(index)) {

return false;

}

// 所有字符都已匹配,返回 true

if (index == word.length() - 1) {

return true;

}

visited[x][y] = true;

boolean result = false;

for (int[] direction : directions) {

int newX = x + direction[0], newY = y + direction[1];

if (newX >= 0 && newX < m && newY >= 0 && newY < n && !

visited[newX][newY]) {

if (dfs(board, word, index + 1, newX, newY, visited)) {

result = true;

break; // 剪枝:一旦有一个方向匹配成功,就中止其他方向的搜索

}

}

}

visited[x][y] = false;

return result;

}

}

以上代码中,我们使用了 visited 数组来标记已经访问过的单元格。我们还使用了一种早

停策略:当在一个方向上的搜索匹配成功时,我们就中止其他方向的搜索。

通过执行结果可以看得出来,剪枝后的效率有所提升。

079.%E5%8D%95%E8%AF%8D%E6%90%9C%E7%B4%A2-20230921151427.png

ACM 模式

为了方便大家理解,我这里把代码加完整,直接以 ACM 的模式(带输入输出)来看一

下。

package com.github.paicoding.forum.test.leetcode;

/**

*

* @author 沉默王二

* @date 9/21/23

*/

import java.util.Scanner;

public class WordSearch {

private static final int[] dirX = {0, 0, 1, -1};

private static final int[] dirY = {1, -1, 0, 0};

private static boolean[][] vis;

private static char[][] map;

private static String str;

public static void main(String[] args) {

Scanner scanner = new Scanner(System.in);

// 输入提示语句

System.out.println("请输入二维数组的行数:");

int rows = scanner.nextInt();

System.out.println("请输入二维数组的列数:");

int cols = scanner.nextInt();

map = new char[rows][cols];

System.out.println("请输入二维数组:");

for (int i = 0; i < rows; i++) {

for (int j = 0; j < cols; j++) {

map[i][j] = scanner.next().charAt(0);

}

}

System.out.println("请输入要查找的字符串:");

str = scanner.next();

System.out.println(exist(map, str));

}

public static boolean exist(char[][] board, String word) {

vis = new boolean[board.length][board[0].length];

map = board;

str = word;

for (int i = 0; i < board.length; i++) {

for (int j = 0; j < board[0].length; j++) {

if (board[i][j] == word.charAt(0)) {

if (DFS(i, j, 0)) {

return true;

}

}

}

}

return false;

}

private static boolean checkMove(int posX, int posY) {

return posX < map.length && posX >= 0 && posY < map[0].length && posY

>= 0 && !vis[posX][posY];

}

private static boolean DFS(int posX, int posY, int step) {

if (map[posX][posY] != str.charAt(step)) return false;

if (step == str.length() - 1) return true;

vis[posX][posY] = true;

for (int i = 0; i < 4; i++) {

int nextX = posX + dirX[i];

int nextY = posY + dirY[i];

if (checkMove(nextX, nextY) && DFS(nextX, nextY, step + 1)) {

return true;

}

}

vis[posX][posY] = false;

return false;

}

}

输入示例如下所示:

请输入二维数组的行数:

3

请输入二维数组的列数:

4

请输入二维数组:

ABCE

SFCS

ADEE

请输入要查找的字符串:

ABCCEDF

true

总结

这类递归回溯的算法,最需要注意的地方就是,在回溯的时候,必须要还原“事故现场”,

让一切都回到原点,接下来才能做出更好的选择。

力扣链接:https://leetcode.cn/problems/word-search/

更新: 2023-09-21 07:31:26

原文: https://www.yuque.com/itwanger/czfoq9/eebn8r

开通会员 本次下载免费

所有资料全部免费下载! 推荐用户付费下载获取返佣积分! 积分可以兑换商品!
一键复制 下载文档 联系客服