「迷宫」系列
490. 迷宫(Medium)
Solu:BFS / DFS
- 只遍历每一次move的终点
- mark
'2':虽然是一次move的终点,但是已经遍历过了,不走回头路- PS:不能将visited的位置mark成
'1',否则会改变grid的原有“地形”
- PS:不能将visited的位置mark成
Code 1:BFS
class Solution:
def hasPath(self, maze: List[List[int]], start: List[int], destination: List[int]) -> bool:
def bfs(i, j):
queue = [(i, j)]
maze[i][j] = 2 # mark visited
while queue:
i, j = queue.pop(0)
for dx, dy in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
x, y = i, j
while 0 <= x + dx < m and 0 <= y + dy < n and maze[x + dx][y + dy] != 1:
x += dx
y += dy
if [x, y] == destination:
return True
if maze[x][y] != 2:
queue.append((x, y))
maze[x][y] = 2
return False
m, n = len(maze), len(maze[0])
return bfs(start[0], start[1])
Code 2:DFS
class Solution:
def hasPath(self, maze: List[List[int]], start: List[int], destination: List[int]) -> bool:
def dfs(i, j):
if i == destination[0] and j == destination[1]:
return True
maze[i][j] = 2 # mark visited
for dx, dy in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
x, y = i, j
while 0 <= x + dx < len(maze) and 0 <= y + dy < len(maze[0]) and maze[x + dx][y + dy] != 1:
x += dx
y += dy
if maze[x][y] == 0 and dfs(x, y):
return True
return False
return dfs(start[0], start[1])
505. 迷宫 II(Medium)
Solu:Dijkstra
- 只对每一次move的终点去更新dp table
Code:
class Solution:
def shortestDistance(self, maze: List[List[int]], start: List[int], destination: List[int]) -> int:
def dijkstra():
min_heap = []
heapq.heappush(min_heap, (0, start[0], start[1]))
while min_heap:
cur_dist, i, j = heapq.heappop(min_heap)
if i == destination[0] and j == destination[1]: # found destination, 提前结束
return cur_dist
if dist[i][j] < cur_dist: # source->[i,j]的shortest path已经被更新过了
continue
for di, dj in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
ni, nj = i, j
while 0 <= ni + di < m and 0 <= nj + dj < n and maze[ni + di][nj + dj] == 0:
ni += di
nj += dj
step = abs(ni - i) + abs(nj - j)
if step + dist[i][j] < dist[ni][nj]:
dist[ni][nj] = step + dist[i][j]
heapq.heappush(min_heap, (dist[ni][nj], ni, nj))
m, n = len(maze), len(maze[0])
dist = [[float('inf')] * n for _ in range(m)]
dist[start[0]][start[1]] = 0
dijkstra()
return dist[destination[0]][destination[1]] if dist[destination[0]][destination[1]] < float('inf') else -1
499. 迷宫 III(Hard)
Solu:Dijkstra
- 单源最短路径用
dijkstra - dp table的修改规则:找到更短的一条path OR 这条path在相同的长度下拥有更小的字典序
Code:
class Solution:
def findShortestWay(self, maze: List[List[int]], ball: List[int], hole: List[int]) -> str:
def dijkstra():
min_heap = []
heapq.heappush(min_heap, (0, [ball[0], ball[1]], ''))
while min_heap:
cur_dist, cur_pos, cur_path = heapq.heappop(min_heap)
i, j = cur_pos[0], cur_pos[1]
if cur_pos == hole:
return
if cur_dist > dist[i][j]:
continue
for dir in dirs:
di, dj = dirs[dir][0], dirs[dir][1]
ni, nj = i, j
while 0 <= ni + di < m and 0 <= nj + dj < n and maze[ni + di][nj + dj] == 0:
ni += di
nj += dj
if [ni, nj] == hole:
break
step = abs(ni - i) + abs(nj - j)
if (step + cur_dist < dist[ni][nj]) or (
step + cur_dist == dist[ni][nj] and cur_path + dir < paths[ni][nj]):
dist[ni][nj] = step + cur_dist
paths[ni][nj] = cur_path + dir
heapq.heappush(min_heap, (step + cur_dist, [ni, nj], cur_path + dir))
m, n = len(maze), len(maze[0])
dirs = {'u': (-1, 0), 'd': (1, 0), 'l': (0, -1), 'r': (0, 1)}
dist = [[float('inf')] * n for _ in range(m)]
paths = [['impossible'] * n for _ in range(m)]
dist[ball[0]][ball[1]] = 0
paths[ball[0]][ball[1]] = ''
dijkstra()
return paths[hole[0]][hole[1]]
特殊的「最短路径」
407. 接雨水 II(Hard)
Solu:Dijkstra + 优先队列
- 最外层的一圈(边界)是不会接到任何雨水的(会从边界流出)
- 「路径高度」:从点
(x, y)到边界的路径中出现的最大高度,必定有路径高度h ≥ heightMap[x][y]=> Dijkstra - 问题的本质:从点
(x, y)到边界的所有「路径高度」的最小值为多少- 根据「木桶理论」,
点(x, y)的最终高度 = min{(x, y)四个邻点的最终高度}
- 根据「木桶理论」,
- *PS:可使用
visited优化:因为Dijkstra中本身使用的最小堆,可以确保「出队元素」是「待更新的元素」的具有最小高度的邻点,并且会把「待更新元素」会被直接更新为“最终高度”
Code 1: Dijkstra + visited优化 ❤️
class Solution:
def trapRainWater(self, heightMap: List[List[int]]) -> int:
m, n = len(heightMap), len(heightMap[0])
def dijkstra():
pq = []
visited = [[False] * n for _ in range(m)]
# 边界不回储水,直接入queue
for i in range(m):
heapq.heappush(pq, (heightMap[i][0], i, 0))
heapq.heappush(pq, (heightMap[i][n - 1], i, n - 1))
for j in range(1, n - 1):
heapq.heappush(pq, (heightMap[0][j], 0, j))
heapq.heappush(pq, (heightMap[m - 1][j], m - 1, j))
# 开始dijkstra
ans = 0
while pq:
h, i, j = heapq.heappop(pq)
for x, y in [(i - 1, j), (i + 1, j), (i, j - 1), (i, j + 1)]:
if 0 < x < m - 1 and 0 < y < n - 1 and not visited[x][y]:
visited[x][y] = True
ans += max(0, h - heightMap[x][y])
heapq.heappush(pq, (max(h, heightMap[x][y]), x, y))
return ans
return dijkstra()
Code 2:传统Dijkstra,无visited
class Solution:
def trapRainWater(self, heightMap: List[List[int]]) -> int:
m, n = len(heightMap), len(heightMap[0])
def dijkstra():
pq = []
height = [[float('inf')] * n for _ in range(m)]
# 边界不回储水,直接入queue
for i in range(m):
heapq.heappush(pq, (heightMap[i][0], i, 0))
heapq.heappush(pq, (heightMap[i][n - 1], i, n - 1))
height[i][0] = heightMap[i][0]
height[i][n - 1] = heightMap[i][n - 1]
for j in range(1, n - 1):
heapq.heappush(pq, (heightMap[0][j], 0, j))
heapq.heappush(pq, (heightMap[m - 1][j], m - 1, j))
height[0][j] = heightMap[0][j]
height[m - 1][j] = heightMap[m - 1][j]
# 开始dijkstra
while pq:
cur_height, i, j = heapq.heappop(pq)
if cur_height > height[i][j]:
continue
for x, y in [(i - 1, j), (i + 1, j), (i, j - 1), (i, j + 1)]:
if 0 < x < m - 1 and 0 < y < n - 1:
new_height = max(cur_height, heightMap[x][y])
if new_height < height[x][y]:
height[x][y] = new_height
heapq.heappush(pq, (new_height, x, y))
return height
height = dijkstra()
return sum(height[i][j] - heightMap[i][j] for i in range(m) for j in range(n))
1631. 最小体力消耗路径(Medium)
Solu:Dijkstra
重要结论:当题目允许往任意方向移动时,考察的往往就不是 DP 了!!而是图论!!
- 把每个格子当成一个node,两个相邻node之间edge的权重 = 两个相邻格子之间的高度差绝对值
- 题目转化为:需要找到一条从左上角到右下角的「最短路径」,其中
len(path) = max{edge's weight in the path}=> Dijkstra
- 题目转化为:需要找到一条从左上角到右下角的「最短路径」,其中
Code:
class Solution:
def minimumEffortPath(self, heights: List[List[int]]) -> int:
m, n = len(heights), len(heights[0])
def dijkstra() -> int:
efforts = [[float('inf')] * n for _ in range(m)]
efforts[0][0] = 0
pq = []
heapq.heappush(pq, (0, 0, 0))
while pq:
cur_effort, i, j = heapq.heappop(pq)
if [i, j] == [m - 1, n - 1]:
return cur_effort
if cur_effort > efforts[i][j]:
continue
for x, y in [(i - 1, j), (i + 1, j), (i, j - 1), (i, j + 1)]:
if 0 <= x < m and 0 <= y < n:
new_effort = max(cur_effort, abs(heights[i][j] - heights[x][y]))
if new_effort < efforts[x][y]:
efforts[x][y] = new_effort
heapq.heappush(pq, (new_effort, x, y))
return -1
return dijkstra()