网格中内有不同的数字,数字表示某种特性,求解从起点到终点所需的最小值。
该类问题常用广度优先搜索方法BFS,优化可采用双向BFS,另外也可以尝试使用DP。
该类型题可以总结出以下模板:
def bfs(nums):
m = len(nums); n = len(nums[0])
visited = {(0, 0)} # 记录已访问的方格
q = [(0, 0, 1)] # 括号里面前两个数字表示已访问的方格的坐标,第三个数表示到达当前方格所需要是操作步数
direction = [(1, 0), (0, 1), (-1, 0), (0, -1)] # 搜索的方向
for a, b, dis in q:
if a == m - 1 and b == n - 1: return dis # 达到终点时返回dis
for i, j in direction:
a_new = a + i; b_new = b + j
if 0<= a_new < m and 0 <= b_new < n and nums[a_new][b_new] == 0: # 判断满足条件时,将满足的方格加入到visited和q中,同时dis需要进行加一操作
visited.add((a_new, b_new))
q.append((a_new, b_new, dis + 1))
return -1 # 以上for循环没有输出,说明得不到答案,执行到这里返回-1
另外一个版本:
def bfs(nums):
m = len(nums); n = len(nums[0])
visited = {(0, 0)} # 记录已访问的方格
from collections import deque
q = deque([(0, 0, 1)])
direction = [(1, 0), (0, 1), (-1, 0), (0, -1)]
while q:
for _ in range(len(q)):
a, b, dis = q.popleft()
if a == m - 1 and b == n - 1: return dis
for i, j in direction:
a_new = a + i; b_new = b + j
if 0<= a_new < m and 0 <= b_new < n and nums[a_new][b_new] == 0:
visited.add((a_new, b_new))
q.append((a_new, b_new, dis + 1))
return -1
二进制矩阵中的最短路径
# BFS
class Solution:
def shortestPathBinaryMatrix(self, grid: List[List[int]]) -> int:
n = len(grid)
if grid[0][0] or grid[n-1][n-1]:
return -1
q = [(0, 0, 1)]
grid[0][0] = 1 # 这里直接将grid进行修改,替换了visited集合
for i, j, d in q:
if i == n-1 and j == n-1: return d
for x, y in ((i-1,j-1),(i-1,j),(i-1,j+1),(i,j-1),(i,j+1),(i+1,j-1),(i+1,j),(i+1,j+1)):
if 0 <= x < n and 0 <= y < n and not grid[x][y]:
grid[x][y] = 1
q.append((x, y, d+1))
return -1
# BFS
class Solution:
def shortestPathBinaryMatrix(self, grid: List[List[int]]) -> int:
n = len(grid)
if n == 1: return 1
if grid[0][0] == 1 or grid[-1][-1] == 1:
return -1
x = [-1, -1, -1, 0, 0, 1, 1, 1]
y = [-1, 0, 1, -1, 1, -1, 0, 1]
path = collections.deque([(0, 0, 1)])
while path:
for _ in range(len(path)):
a, b, dis = path.popleft()
if a == n - 1 and b == n - 1: return dis
for i, j in zip(x, y):
new_a, new_b = a + i, b + j
if 0 <= new_a < n and 0 <= new_b < n and grid[new_a][new_b] == 0:
grid[new_a][new_b] = -1
path.append((new_a, new_b, dis + 1))
return -1
以下为双向BFS求解,代码不同的地方就是设计了三个集合,start、end和next_start,每一次的for循环找到的可行路径加入到next_start中,最后将其赋值给start,最后根据start与end的长短,将其进行调换。需要注意的是,这里需要在最内部的for循环中判断if (new_a, new_b) in end: return dis
# 双向BFS
class Solution:
def shortestPathBinaryMatrix(self, grid: List[List[int]]) -> int:
n = len(grid)
if n == 1: return 1
if grid[0][0] or grid[-1][-1]== 1: return -1
tuple_offset = ((1, 0), (0, 1), (1, 1), (-1, 0), (0, -1), (1, -1), (-1, 1), (-1, -1))
start = {(0, 0)}
end = {(n - 1, n - 1)}
dis = 1
while start:
dis += 1
next_start = set()
for x, y in start:
for x_offset, y_offset in tuple_offset:
new_a, new_b = x + x_offset, y + y_offset
# 两端碰撞
if (new_a, new_b) in end: return dis
if 0 <= new_a < n and 0 <= new_b < n and grid[new_a][new_b] == 0:
grid[new_a][new_b] = -1
next_start.add((new_a, new_b))
start = next_start
if len(end) < len(start):
start, end = end, start
return -1
该题也可以采用动态规划的思路进行求解。
DP 解题思路(已通过所有测试样例) python3
import copy
class Solution:
def shortestPathBinaryMatrix(self, grid: List[List[int]]) -> int:
# 逻辑是 dp[i][j] = arround_min(i,j) + 1
dx = [-1, -1, -1, 0, 0, 1, 1, 1]
dy = [-1, 0, 1, 1, -1, -1, 0, 1]
dist = 0
N = len(grid)
# 边界条件
if grid[0][0] == 1 or grid[-1][-1] == 1:return -1
if N == 1 :return 1
# 将不可走路径设为inf
for i in range(N):
for j in range(N):
if grid[i][j] == 0:
grid[i][j] = inf
else: grid[i][j] = 'stop'
grid[0][0] = 1
# 定义一个arround_min函数
def arround_min(i, j):
_min = inf
for k in range(8):
x, y = i + dx[k], j + dy[k]
if (0 <= x < N and 0 <= y < N and grid[x][y] != 'stop'):
_min = min(_min, grid[x][y])
return _min
#这里必须深拷贝,因为是二维数组
gridnew = copy.deepcopy(grid)
while(True):
#终止条件
if grid[-1][-1] != inf:
return grid[-1][-1]
#循环
for j in range(N):
for i in range(N):
if i == 0 and j == 0:
continue
if grid[i][j] != 'stop':
grid[i][j] = arround_min(i, j) + 1
# 判断grid是否还有变化,若没有则证明无解
if gridnew == grid:
return -1
gridnew = copy.deepcopy(grid)
穿过迷宫的最少移动次数
注意:第一、二条规则是指蛇可以进行平移(当不存在障碍物的时候)
该题与上一题不同的是,本题没有特定的dircetion,需要根据周围障碍物的情况而进行方向的选择,因此需要进行枚举,代码如下:
class Solution:
def minimumMoves(self, grid: List[List[int]]) -> int:
n = len(grid)
queue = [(0, 0, 0, 1, 0)]
visit = {(0, 0, 0, 1)}
for a1, b1, a2, b2, step in queue:
if (a1, b1, a2, b2) == (n - 1, n - 2, n - 1, n -1): return step
if a1 + 1 < n and a2 + 1 < n and grid[a1 + 1][b1] == grid[a2 + 1][b2] == 0:
if (a1 + 1, b1, a2 + 1, b2) not in visit:
visit.add((a1 + 1, b1, a2 + 1, b2))
queue.append((a1 + 1, b1, a2 + 1, b2, step + 1))
if a1 == a2 and (a1, b1, a1 + 1, b1) not in visit:
visit.add((a1, b1, a1 + 1, b1))
queue.append((a1, b1, a1 + 1, b1, step + 1))
if b1 + 1 < n and b2 + 1 < n and grid[a1][b1 + 1] == grid[a2][b2 + 1] == 0:
if (a1, b1 + 1, a2, b2 + 1) not in visit:
visit.add((a1, b1 + 1, a2, b2 + 1))
queue.append((a1, b1 + 1, a2, b2 + 1, step + 1))
if b1 == b2 and (a1, b1, a1, b2 + 1) not in visit:
visit.add((a1, b1, a1, b2 + 1))
queue.append((a1, b1, a1, b2 + 1, step + 1))
return -1
网格中的最短路径
该题由于可以对障碍物进行清除,因此不能在原有的数组中进行修改,即需要设计一个visited数组已表示已访问的网格。
class Solution:
def shortestPath(self, grid: List[List[int]], k: int) -> int:
m, n = len(grid), len(grid[0])
if k >= m + n - 3: return m + n - 2
q = [(0, 0, 0, 0)]
visited = {(0, 0, 0)}
direct = [(1, 0), (0, 1), (-1, 0), (0, -1)]
for i, j, w, step in q:
if step > k: continue
if i == m - 1 and j == n - 1: return w
for a, b in direct:
i_new = i + a
j_new = j + b
if 0 <= i_new < m and 0<= j_new < n:
step_new = step + 1 if grid[i_new][j_new] == 1 else step
if (i_new, j_new, step_new) not in visited:
visited.add((i_new, j_new, step_new))
q.append((i_new, j_new, w + 1, step_new))
return -1
迷宫
在迷宫中有一个球,里面有空的空间和墙壁。球可以通过滚上,下,左或右移动,
但它不会停止滚动直到撞到墙上。当球停止时,它可以选择下一个方向。
给定球的起始位置,目的地和迷宫,确定球是否可以停在终点。
迷宫由二维数组表示。1表示墙和0表示空的空间。你可以假设迷宫的边界都是墙。开始和目标坐标用行和列索引表示。
例:
输入:
map =
[
[0,0,1,0,0],
[0,0,0,0,0],
[0,0,0,1,0],
[1,1,0,1,1],
[0,0,0,0,0]
]
start = [0,4]
end = [3,2]
输出:
false
def bfs(nums, start, end):
m = len(nums)
n = len(nums[0])
if not m or not n: return True
visited = {(start[0], start[1])}
q = [(start[0], start[1])]
direction = [(1, 0), (0, 1), (-1, 0), (0, -1)]
for x, y in q:
# for a, b in qe:
if x == end[0] and y == end[1]: return True
for i, j in direction:
a = x; b = y # 注意此处需要复制一份a、b,确保每一次while循环a、b的值都能为(x, y)!!!!
while 0 <= a < m and 0 <= b < n and nums[a][b] == 0:
a += i
b += j
a -= i
b -= j
if (a, b) not in visited:
visited.add((a, b))
q.append((a, b))
return False
map = [[0,0,1,0,0],
[0,0,0,0,0],
[0,0,0,1,0],
[1,1,0,1,1],
[0,0,0,0,0]]
start = [0, 4]
end = [4, 4]
a = bfs(map, start, end)
print(a)
迷宫II
在迷宫中有一个球,里面有空的空间和墙壁。球可以通过滚上,下,左或右移动,但它不会停止滚动直到撞到墙上。当球停止时,它可以选择下一个方向。
给定球的起始位置,目标和迷宫,找到最短距离的球在终点停留。距离是由球从起始位置(被排除)到目的地(包括)所走过的空空间的数量来定义的。如果球不能停在目的地,返回-1。
迷宫由二维数组表示。1表示墙和0表示空的空间。你可以假设迷宫的边界都是墙。开始和目标坐标用行和列索引表示。
Example 1:
Input:
(rowStart, colStart) = (0,4)
(rowDest, colDest)= (4,4)
0 0 1 0 0
0 0 0 0 0
0 0 0 1 0
1 1 0 1 1
0 0 0 0 0
Output: 12
Explanation:
(0,4)->(0,3)->(1,3)->(1,2)->(1,1)->(1,0)->(2,0)->(2,1)->(2,2)->(3,2)->(4,2)->(4,3)->(4,4)
Example 2:
Input:
(rowStart, colStart) = (0,4)
(rowDest, colDest)= (0,0)
0 0 1 0 0
0 0 0 0 0
0 0 0 1 0
1 1 0 1 1
0 0 0 0 0
Output: 6
Explanation:
(0,4)->(0,3)->(1,3)->(1,2)->(1,1)->(1,0)->(0,0)
这一题与上一题的代码基本一样,只是需要一个额外的变量记录所走的路程,因此将该变量加入到q列表中。代码如下:
def bfs(nums, start, end):
m = len(nums)
n = len(nums[0])
if not m or not n: return True
visited = {(start[0], start[1])}
q = [(start[0], start[1], 0)]
direction = [(1, 0), (0, 1), (-1, 0), (0, -1)]
for x, y, dis in q:
# for a, b in qe:
if x == end[0] and y == end[1]: return dis
for i, j in direction:
a = x; b = y; d = dis# 注意此处需要复制一份a、b,确保每一次while循环的a、b并值都能为(x, y)!!!!
while 0 <= a < m and 0 <= b < n and nums[a][b] == 0:
a += i
b += j
d += 1
a -= i
b -= j
d -= 1
if (a, b) not in visited:
visited.add((a, b))
q.append((a, b, d))
return -1
map = [[0,0,1,0,0],
[0,0,0,0,0],
[0,0,0,1,0],
[1,1,0,1,1],
[0,0,0,0,0]]
start = [0, 4]
end = [4, 4]
a = bfs(map, start, end)
print(a)
以上题均只是计算最后的结果,如何输出路径?在一些笔试中,常会对路径进行限制,(如一次华为的笔试,在连续的直线路径上,可以一下跳跃两步等等类似的情况)这时候就可以先求出路径,然后再对所选择的路径再进行处理(目前我想的是这样处理,有没有一种方法,在寻路径的过程就进行计算,本人还没有想到,希望后续能够学习到)
这里就先放别人的实现,参考链接。但是这种目前用在采用动态规划方法中比较好理解,但是如果题目是如上几种,采用的是BFS,该路径如何打印?本人也一直很无解-_-