ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

【回溯】力扣79:单词搜索

2022-08-13 12:32:40  阅读:187  来源: 互联网

标签:index word return len 力扣 board 回溯 匹配 79


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

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

示例:

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

看到搜索,想到 DFS,从而考虑是否需要回溯

  • 排列、组合、选择类问题使用回溯法比较方便

看到相邻,想到 方向数组directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]

此题为选择类问题。对于已访问的路径标记,可以维护一个 visited 的dict(tuple),先标记当前位置为已访问,以避免重复遍历(如防止向右搜索后又向左返回);在所有的可能都搜索完成后,再回改当前位置为未访问,防止干扰其它位置搜索到当前位置。


  1. 外层:遍历
    首先遍历给定网格 board 的所有元素,先找到和单词 word 第一个字母相同的元素,然后进入递归流程。

    在外层对每一个位置 (i, j) 都调用函数 check(i, j, 0) 进行检查:只要有一处返回 true,就说明网格中能够找到相应的单词,否则说明不能找到,返回 false

  2. 内层:递归
    定义搜索函数 check(x, y, index) 来判断从 (x, y) 出发,能否搜索到单词 word[index..],其中 word[index..] 表示字符串 word 从第 index 个字符开始的后缀子串。如果能搜索到,则返回 true,反之返回 false。

    函数 check(x, y, index)的执行步骤如下:

    • 如果 board[x][y] ≠ word[index],当前字符不匹配,直接返回 false

    • 如果当前已经访问到字符串的末尾 且对应字符依然匹配 (index == len(word) - 1),此时直接返回 true;否则,朝它的上下左右试探,看看它周边的这四个元素是否能匹配 word 的下一个字母

      • 如果匹配:带着该元素继续进入下一个递归
      • 如果上下左右全部都不匹配:返回 false
  3. 注意点

  • 递归时元素的坐标是否超过边界

  • 回溯标记 visited[x][y] 以及 返回 的时机

有标记数组

标记数组 visited 可以初始化为空,遇到一个结点就加进来,表示已访问,如果相邻路径都走不通再踢出去

class Solution:
    def exist(self, board: List[List[str]], word: str) -> bool:
        n = len(word)
        if n == 0:
            return True
        if not board and word:
            return False

        row, col = len(board), len(board[0])
        directions = [(0, 1), (0, -1), (1, 0), (-1, 0)] # 方向数组,里面用小括号中括号不影响判断
        visited = set() # 已访问数组

        def check(x, y, index):
            if board[x][y] != word[index]: # 一旦不匹配就返回false
                return False
            if index == n - 1: # 已经遍历到单词末尾还匹配,说明全部匹配,返回true
                return True
            visited.add((x, y)) # 不满足第一个判断条件就说明匹配,将该结点标记为已访问
            for dx, dy in directions:
                nx, ny = x + dx, y + dy
                if 0 <= nx <row and 0 <= ny < col:
                    if (nx, ny) not in visited and check(nx, ny, index + 1):
                        return True
                        break
            visited.remove((x, y))
            return False

        for i in range(row):
            for j in range(col):
                if check(i, j, 0):
                    return True
        return False

标记数组 visited 也可以先初始化为与 board 等大的数组,发现当前访问结点与字符串第一个元素匹配就标记为已访问visited[i][j] = 1,如果递归发现不完全匹配再取消标记visited[i][j] = 0

class Solution:
    def exist(self, board: List[List[str]], word: str) -> bool:
        n = len(word)
        if n == 0:
            return True
        if not board and word:
            return False

        row, col = len(board), len(board[0])
        directions = [(0, 1), (0, -1), (1, 0), (-1, 0)] # 方向数组,里面用小括号中括号不影响判断
        visited = [[0 for _ in range(col)] for _ in range(row)] # 与 board 等大的已访问数组

        def check(x, y, index):
            if board[x][y] != word[index]: # 一旦不匹配就返回false
                return False
            if index == n - 1: # 已经遍历到单词末尾还匹配,说明全部匹配,返回true
                return True
            for dx, dy in directions:
                nx, ny = x + dx, y + dy
                if 0 <= nx <row and 0 <= ny < col:
                    if visited[nx][ny] == 1: # 如果是已访问的元素就忽略
                        continue
                    visited[nx][ny] = 1 # 如果是未访问的元素就标记为已访问
                    if check(nx, ny, index + 1):
                        return True
                    else: # 回溯
                        visited[nx][ny] = 0
            return False

        for i in range(row):
            for j in range(col):
                if board[i][j] == word[0]: # 如果与字符串第一个元素匹配就标记为已访问
                    visited[i][j] = 1
                    if check(i, j, 0):
                        return True
                    else: # 回溯
                        visited[i][j] = 0
        return False

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

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

无标记数组

题目没有明确说不能修改原数组,那么修改原数组不失为更简洁省空间的方式。

在辅函数中,如果 board[x][y] 匹配了 word[index],就标记该结点为已访问状态board[x][y] = '#',避免反过来再检测。然后对相邻位置进行递归查找,如果周边结点都检测一遍都为false了,再将这个结点还原board[x][y] = word[index],接着到下一个结点比较。

class Solution:
    def exist(self, board: List[List[str]], word: str) -> bool:
        n = len(word)
        if n == 0:
            return True
        if not board and word:
            return False

        row, col = len(board), len(board[0])
        directions = [[0,1], [0,-1], [1,0], [-1,0]] # 方向数组,里面用小括号中括号不影响判断

        def check(x, y, index):
            if board[x][y] != word[index]: # 一旦不匹配就返回false
                return False
            if index == n - 1: # 已经遍历到单词末尾还匹配,说明全部匹配,返回true
                return True
            board[x][y] = '#' # 标记为已访问
            for direct in directions:
                nx, ny = x + direct[0], y + direct[1] # 注意这里是derect,不是directions
                if 0 <= nx <row and 0 <= ny < col and check(nx, ny, index + 1):
                    return True
            board[x][y] = word[index] # 如果路径走不通,取消标记,还原结点

        for i in range(row):
            for j in range(col):
                if check(i, j, 0):
                    return True
        return False

标签:index,word,return,len,力扣,board,回溯,匹配,79
来源: https://www.cnblogs.com/Jojo-L/p/16582713.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有