1. 题目
你准备参加一场远足活动。给你一个二维 rows x columns
的地图 heights
,其中 heights[row][col]
表示格子 (row, col)
的高度。一开始你在最左上角的格子 (0, 0)
,且你希望去最右下角的格子 (rows-1, columns-1)
(注意下标从 0 开始编号)。你每次可以往 上,下,左,右 四个方向之一移动,你想要找到耗费 体力 最小的一条路径。
一条路径耗费的 体力值 是路径上相邻格子之间 高度差绝对值 的 最大值 决定的。
请你返回从左上角走到右下角的最小 体力消耗值 。
2. 示例
示例 1:
输入:heights = [[1,2,2],[3,8,2],[5,3,5]] 输出:2 解释:路径 [1,3,5,3,5] 连续格子的差值绝对值最大为 2 。 这条路径比路径 [1,2,2,2,5] 更优,因为另一条路径差值最大值为 3 。
示例 2:
输入:heights = [[1,2,3],[3,8,4],[5,3,5]] 输出:1 解释:路径 [1,2,3,4,5] 的相邻格子差值绝对值最大为 1 ,比路径 [1,3,5,3,5] 更优。
示例 3:
输入:heights = [[1,2,1,1,1],[1,2,1,2,1],[1,2,1,2,1],[1,2,1,2,1],[1,1,1,2,1]] 输出:0 解释:上图所示路径不需要消耗任何体力。
提示:
rows == heights.length
columns == heights[i].length
1 <= rows, columns <= 100
1 <= heights[i][j] <= 106
3. 思路
这题我刚开始以为是动态规划的题目,因为这个和我之前做的一个题目很像,但看到第三个示例的图我才知道这是图搜索类型的题目,而说到图搜索,我们第一反应想到的是BFS(广度优先搜索)或DFS(深度优先搜索)这两种算法。因此这题我打算采用BFS算法;但是该如何进行BFS呢?
首先,我们可以定义一个二元组列表,用来模拟上下左右移动,然后我们可以用二分查找算法来求最终的结果。这里就要提到为什么用二分查找,因为我们要查找最小体力消耗路径,而体力消耗是这条路径上最大的高度差,因此我们可以用一个x_0来定义当前路径最大允许的高度差,如果在该情况下可以到达终点,则说明结果ans <= x_0,即x_0大了,需要减少x_0的值;而如果不能到达终点,说明我们的x_0小了,需要增大x_0的值。因此这里我们可以用二分查找来寻找最小的x_0。
然后在每次x_0(当前允许的最大高度差)的基础上,我们定义一个队列来实现BFS算法,我们将下一个移动的点加入队列的条件有哪些呢?
- 0 <= x <= rows - 1
- 0 <= y <= cols - 1
- (new_x,new_y) 没有访问过
4. 代码
class Solution:
def minimumEffortPath(self, heights: List[List[int]]) -> int:
rows, columns = len(heights), len(heights[0])
move = [(0, -1), (-1, 0), (0, 1), (1, 0)] # 左上右下
ans, left, right = 0, 0, 10 ** 6 - 1
while left <= right:
x_0 = (left + right) // 2 # 当前路线的最大允许高度差
queue = [(0, 0)] # 起点
visited = {(0, 0), } # 访问集合
# 当队列不为空
while queue:
current = queue.pop(0) # 出队
# # 移动
for x, y in move:
# 下一个点的x,y坐标
new_x, new_y = current[0] + x, current[1] + y
# 如果不在visited集合中 且 坐标合理 且 当前高度差小于允许高度差
if (new_x, new_y) not in visited and 0 <= new_x < rows and 0 <= new_y < columns and abs(
heights[current[0]][current[1]] - heights[new_x][new_y]) <= x_0:
queue.append((new_x, new_y)) # 加入队列
visited.add((new_x, new_y)) # 加入访问集合,之后不再访问
# 如果到了最右下角则退出(终点)
if current[0] == rows - 1 and current[1] == columns - 1:
ans = x_0 # 重置结果值
break
# 如果当前路线可以到达终点
if ans == x_0:
right = x_0 - 1 # 减少右边界的值
else:
left = x_0 + 1 # 增加左边界的值
return ans