题目:
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
例如,在下面的 3×4 的矩阵中包含单词 "ABCCED"(单词中的字母已标出)。
示例 1:
输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true
示例 2:
输入:board = [["a","b"],["c","d"]], word = "abcd"
输出:false
提示:
1 <= board.length <= 200
1 <= board[i].length <= 200
board 和 word 仅由大小写英文字母组成。
解析:
这道题是典型的DFS(深度优先搜索)问题,在一个图中去搜索符合条件的的路径,DFS是从某一个起始点开始沿着某一条路径开始走,直到不能走为止,然后再往回走。一条路走到底,不撞南墙不回头。这样很符合递归的思想,一直递归到底,然后遇到终止条件,回溯即可。本题也是使用递归的方式(推荐), 不过也给出了非递归的方式(用栈实现),来一步步的实现DFS,更加直观,不过代码就比较冗杂。
第一种方法:递归法,DFS 通过递归,先朝一个方向搜到底,再回溯至上个节点,沿另一个方向搜索,以此类推。
def exist(board, word: str) -> bool:
# 找到路径起始,然后枚举,DFS。
# 首先找出来第一个元素,之后从第一个元素起始,进行DFS。
# 将已经访问过的节点变为“0”,保证以后再回到该节点时不会被访问。
# 然后回退,把之前做出的选择撤销,还原。
def dfs(i,j,k):
if i<0 or i>len(board)-1 or j<0 or j>len(board[0])-1: #越界终止条件
return False
if board[i][j] != word[k]: #矩阵元素与目标字符不同或者当前矩阵元素已访问过(要是节点访问过时,与word中的元素也会不同,因为访问过就为"0",所以可以将这两个终止条件合并)
return False
if k == len(word)-1:
return True
board[i][j] = "0"
#朝当前元素的 上、下、左、右 四个方向开启下层递归
res = dfs(i,j+1,k+1) or dfs(i,j-1,k+1) or dfs(i+1,j,k+1) or dfs(i-1,j,k+1)
board[i][j] = word[k] #回退:如果当前路径上正在访问的这个节点满足board[a][b] = word[k],但是这一节点相邻的三个方向都不满足要求,就需要把这个点回退。
return res
index = 0
for i in range(len(board)):
for j in range(len(board[0])):
if board[i][j] == word[0]: #首先找到路径的起始节点
if dfs(i,j,index): #然后DFS即可
return True
return False
第二种方法:非递归法,利用栈的先进后出原则,实现深度优先遍历,顺着一节点开始一直往下走,走不动了再回溯回来。
# 深度优先搜索遍历(DFS)---非递归方式(便于理解过程)--用栈。
def exist(board, word: str) -> bool:
stack_1, stack_2 = [], [] #两个栈,一个栈保存所有路径,一个节点的上下左右;另外一个栈保存跟题目要求路径相同的节点,也就是已经访问的节点(来记录之前一步的位置)。
index = 0
if len(word) == 0:
return False
#确定首节点,加入栈1中(也就是确定要求路径首节点在矩阵中的位置,然后从这个位置开始,在相邻节点中进行判断那条路径可以走)
for i in range(len(board)):
for j in range(len(board[0])):
if board[i][j]==word[index]:
stack_1.append((i, j))
while stack_1:
x, y = stack_1[-1][0], stack_1[-1][1] #获取栈顶元素指针x,y
if x < 0 or x > len(board) - 1 or y < 0 or y > len(board[0]) - 1: #当栈顶元素指针越界时,直接弹出该元素指针即可
stack_1.pop()
elif board[x][y] == word[index] and board[x][y] != "0": #当矩阵中的当前节点和要求路径节点相同并且没有访问过的时候
if index == len(word) - 1: #当指针移动到要求路径的末尾时,证明在矩阵中找到了要求路径。
return True
board[x][y] = "0" #当前节点等于要求路径节点时,设置为0,证明已经访问过。
index += 1 #index继续前进,获得路径的下一个点
stack_2.append((x, y)) #加入以访问的节点栈2中,用来记录之前一步的位置
# 将当前节点的上下左右邻接点加入栈1中(也就是当前节点下一步的所有路径)
stack_1.append((x, y + 1))
stack_1.append((x, y - 1))
stack_1.append((x + 1, y))
stack_1.append((x - 1, y))
else:
# 回溯的时候如果回溯到上一步(之前记录的那一步的位置)则还原(当栈1中出栈时(回溯),遇到跟栈2中相同的元素(也就是回溯到了上一步,已经处理完的一步),
# 然后进行还原(将index指针复位,设置为0的节点恢复,将两个栈的栈顶节点退出))(把之前做出的选择撤销)
if stack_2 and stack_2[-1] == stack_1[-1]:
index -= 1
board[x][y] = word[index]
stack_2.pop()
stack_1.pop()
else:
stack_1.pop()
return False #当栈1(矩阵元素)元素出栈完毕,证明在矩阵中没有找到该路径。