You are a hiker preparing for an upcoming hike. You are given heights, a 2D array of size rows x columns, where heights[row][col] represents the height of cell (row, col). You are situated in the top-left cell, (0, 0), and you hope to travel to the bottom-right cell, (rows-1, columns-1) (i.e., 0-indexed). You can move up, down, left, or right, and you wish to find a route that requires the minimum effort.
A route's effort is the maximum absolute difference in heights between two consecutive cells of the route.
Return the minimum effort required to travel from the top-left cell to the bottom-right cell.
Example 1:
Input: heights = [[1,2,2],[3,8,2],[5,3,5]]
Output: 2
Explanation: The route of [1,3,5,3,5] has a maximum absolute difference of 2 in consecutive cells.
This is better than the route of [1,2,2,2,5], where the maximum absolute difference is 3.
Example 2:
Input: heights = [[1,2,3],[3,8,4],[5,3,5]]
Output: 1
Explanation: The route of [1,2,3,4,5] has a maximum absolute difference of 1 in consecutive cells, which is better than route [1,3,5,3,5].
Example 3:
Input: 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]]
Output: 0
Explanation: This route does not require any effort.
// 方向数组,上下左右的坐标偏移量
int[][] dirs = new int[][]{{0,1}, {1,0}, {0,-1}, {-1,0}};
// 返回坐标 (x, y) 的上下左右相邻坐标
List<int[]> adj(int[][] matrix, int x, int y) {
int m = matrix.length, n = matrix[0].length;
// 存储相邻节点
List<int[]> neighbors = new ArrayList<>();
for (int[] dir : dirs) {
int nx = x + dir[0];
int ny = y + dir[1];
if (nx >= m || nx < 0 || ny >= n || ny < 0) {
// 索引越界
continue;
}
neighbors.add(new int[]{nx, ny});
}
return neighbors;
}
类似的,我们现在认为一个二维坐标 (x, y) 是图中的一个节点,所以这个 State 类也需要修改一下:
class State {
// 矩阵中的一个位置
int x, y;
// 从起点 (0, 0) 到当前位置的最小体力消耗(距离)
int effortFromStart;
State(int x, int y, int effortFromStart) {
this.x = x;
this.y = y;
this.effortFromStart = effortFromStart;
}
}
接下来,就可以套用 Dijkstra 算法的代码模板了:
// Dijkstra 算法,计算 (0, 0) 到 (m - 1, n - 1) 的最小体力消耗
int minimumEffortPath(int[][] heights) {
int m = heights.length, n = heights[0].length;
// 定义:从 (0, 0) 到 (i, j) 的最小体力消耗是 effortTo[i][j]
int[][] effortTo = new int[m][n];
// dp table 初始化为正无穷
for (int i = 0; i < m; i++) {
Arrays.fill(effortTo[i], Integer.MAX_VALUE);
}
// base case,起点到起点的最小消耗就是 0
effortTo[0][0] = 0;
// 优先级队列,effortFromStart 较小的排在前面
Queue<State> pq = new PriorityQueue<>((a, b) -> {
return a.effortFromStart - b.effortFromStart;
});
// 从起点 (0, 0) 开始进行 BFS
pq.offer(new State(0, 0, 0));
while (!pq.isEmpty()) {
State curState = pq.poll();
int curX = curState.x;
int curY = curState.y;
int curEffortFromStart = curState.effortFromStart;
// 到达终点提前结束
if (curX == m - 1 && curY == n - 1) {
return curEffortFromStart;
}
if (curEffortFromStart > effortTo[curX][curY]) {
continue;
}
// 将 (curX, curY) 的相邻坐标装入队列
for (int[] neighbor : adj(heights, curX, curY)) {
int nextX = neighbor[0];
int nextY = neighbor[1];
// 计算从 (curX, curY) 达到 (nextX, nextY) 的消耗
int effortToNextNode = Math.max(
effortTo[curX][curY],
Math.abs(heights[curX][curY] - heights[nextX][nextY])
);
// 更新 dp table
if (effortTo[nextX][nextY] > effortToNextNode) {
effortTo[nextX][nextY] = effortToNextNode;
pq.offer(new State(nextX, nextY, effortToNextNode));
}
}
}
// 正常情况不会达到这个 return
return -1;
}