79. 单词搜索

79 篇文章 3 订阅

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

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

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

思路:(回溯暴力搜索)

整体思路
使用深度优先搜索(DFS) 和回溯的思想实现。在访问过程中要设置一个二维数组标记元素 hasVisited 是否被访问过。

外层遍历
遍历 board 的所有元素, 并定义函数,从每个结点内层遍历,传入以下参数;

  • 变量 k 记录已经匹配的长度
  • 该节点的行 r 列 c 坐标
  • 数组的遍历情况 hasVisited
  • 原字符数组 board
  • 需要搜索的字符串 word

内层遍历:递归 + 回溯

  • 定义递归返回条件
  • 边界情况及不符情况判断
  • 如果符合,把该节点标记为已访问
  • 再从该节点出发,从四个方向依次递归调用,并判断
  • 递归结束后,回溯,即将该节点置位未访问

代码:(Java)

public class words_search {

	public static void main(String[] args) {
		// TODO 自动生成的方法存根
		char[][] board = {
				{'A', 'B', 'C', 'E'},
				{'S', 'F', 'C', 'S'},
				{'A', 'D', 'E', 'E'}
		};
		String word = "ABCB";
		System.out.println(exist(board, word));
	}
	private static int [][] dircetions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};//可有走的四个方向
	private static int m;
	private static int n;
	
	public static boolean exist(char[][] board, String word) {
		if(word == null || word.length() == 0) {
			return true;
		}
		if(board == null || board.length == 0 || board[0].length == 0) {
			return false;
		}
		
		m = board.length;//行
		n = board[0].length;//列
		
		boolean [][] hasVisited = new boolean[m][n];//是否被访问

		for(int r = 0; r < m; r++) {
			for(int c = 0; c < n; c++) {
				if(backtracking(0, r, c, hasVisited, board, word)) {//数组中的每一个字母都可以当做首字母
					return true;
				}
			}
		}
		return false;
	}

	private static boolean backtracking(int k, int r, int c, boolean[][] hasVisited, char[][] board, String word) {
		// TODO 自动生成的方法存根
		if(k == word.length()) {//比对完成,返回
			return true;
		}
		if(r < 0 || r >= m || c < 0 || c >= n || board[r][c] != word.charAt(k) || hasVisited[r][c]) {//边界及不符合判断
			return false;
		}
		hasVisited[r][c] = true;//访问
		for(int [] d : dircetions) {//四个方向依次遍历
			if(backtracking(k + 1, r + d[0], c + d[1], hasVisited, board, word)){
				return true;
			}
		}
		hasVisited[r][c] = false;//回溯
		
		return false;
	}
}
输出:

在这里插入图片描述

复杂度分析:
  • 时间复杂度:一个非常宽松的上界为 O ( M N ⋅ 3 L ) O(MN⋅3^L) O(MN3L),其中 M,N为网格的长度与宽度,L 为字符串 word 的长度。在每次调用函数 backtracking 时,除了第一次可以进入 4 个分支以外,其余时间我们最多会进入 3 个分支(因为每个位置只能使用一次,所以走过来的分支没法走回去)。由于单词长为 L ,故 backtracking 的时间复杂度为 O ( 3 L ) O(3^L) O(3L),而我们要执行 O(MN)次检查。然而,由于剪枝的存在,我们在遇到不匹配或已访问的字符时会提前退出,终止递归流程。因此,实际的时间复杂度会远远小于 O ( M N ⋅ 3 L ) O(MN⋅3^L) O(MN3L)

  • 空间复杂度:O(MN)。我们额外开辟了 O(MN) 的 hasVisited 数组,同时栈的深度最大为O(min⁡(L,MN)) 。

注:仅供学习参考!

题目来源:力扣

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

酷酷的懒虫

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值