题目描述
You are a hiker preparing for an upcoming hike. You are given heights, a 2D array of size rows × \times × 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.
Constraints:
- rows == heights.length
- columns == heights[i].length
- 1 <= rows, columns <= 100
- 1 <= heights[i][j] <= 1 0 6 10^6 106
题目来源:https://leetcode-cn.com/problems/path-with-minimum-effort/
题目大意
在本题中,对于某一条路径,其所需花费的体力为所有相邻格子之间的高度差中的最大值,即该条路径的长度。那么,对于输入的rows × \times ×cols二维数组,从起点==(0, 0)==出发,终点是 (rows - 1, cols - 1),题目要求找到一条从起点到终点的花费最小体力的路径,并返回最小体力。
解题方法
这道题的解决办法有很多,目前先介绍下面这几种方法,后续再逐步补充和完善。
方法一:并查集
思路
对于这种与路径相关的题目,核心问题是如何建图,根据题目抽象出一个恰当的图论模型,也就是要回答清楚这几个问题:什么代表图中的节点?什么代表图中的边,边的权值又该如何计算?
在本题中,对于输入的rows
×
\times
×cols二维数组,可抽象成如下的一个图论模型:
- 将输入的二维数组中的每一个格子看成图中的一个节点;并且,为了表示每个节点,需要给每个格子对应的节点赋予一个唯一的节点编号(编号从1开始),对于二维数组中位置为 ( i , j ) (i, j) (i,j)的格子,其对应的节点编号为 i ∗ r o w s + j + 1 i * rows + j + 1 i∗rows+j+1。
- 将相邻(左右相邻或上下相邻)的两个格子对应的节点之间看成存在一条无向边,边的权值为这两个格子高度差的绝对值,即所需花费的体力;并且,无向边可用两个节点的节点编号构成二元组来表示。
基于上述图模型,图的初始状态是所有
r
o
w
s
×
c
o
l
s
rows\times cols
rows×cols个节点是孤立的,起点和终点之间不存在一条路径来将它们相连,由于题目要求我们求出所需花费的最小体力,可将所有边按照其权值进行从小到大的排序,然后依次往图中添加无向边(即并查集的”并“操作),并判断起点和终点是否相连(即并查集的”查“操作),若不相连,则继续添加无向边,若相连,当前添加的无向边的权值即为所求。
类似的题目还有778.Swim in Rising Water,与本题的差异在于,需要将相邻格子的最大值看作边的权值。
代码
class Solution {
// 并查集代码模板
final int N = 10010;
int[] p = new int[N];
public int find(int x){
if(p[x] != x) p[x] = find(p[x]);
return p[x];
}
public void union(int x, int y){
p[find(x)] = find(y);
}
public int minimumEffortPath(int[][] heights) {
int rows = heights.length;
int cols = heights[0].length;
for(int i = 1; i <= rows * cols; i ++) p[i] = i;
// edges[i][0], edges[i][1]分别为构成边i的两个节点的编号
// edges[i][2]为边i的权值,即需花费的体力值
int[][] edges = new int[2 * rows * cols - rows - cols][3];
int k = 0;
for(int i = 0; i < rows; i ++){
for(int j = 0; j < cols; j ++){
if(j + 1 < cols){
edges[k][0] = i * cols + j + 1;
edges[k][1] = i * cols + j + 2;
edges[k][2] = Math.abs(heights[i][j] - heights[i][j + 1]);
k ++;
}
if(i + 1 < rows){
edges[k][0] = i * cols + j + 1;
edges[k][1] = (i + 1) * cols + j + 1;
edges[k][2] = Math.abs(heights[i][j] - heights[i + 1][j]);
k ++;
}
}
}
if(edges.length <= 0) return 0;
Arrays.sort(edges, (o1, o2) -> o1[2] - o2[2]);
int res = 0;
for(int i = 0; i < edges.length; i ++){
union(edges[i][0], edges[i][1]);
if(find(1) == find(rows * cols)){
res = edges[i][2];
break;
}
}
return res;
}
}
复杂度分析
- 时间复杂度: O ( m n l o g ( m n ) ) O(mnlog(mn)) O(mnlog(mn)),其中 m m m、 n n n分别为输入的二维矩阵的行数和列数。在本题中,对于m × \times ×n的二维矩阵,共有m × \times ×n个节点,所构成的边的数量为 O ( m n ) O(mn) O(mn),则对它们进行排序的时间复杂度为 O ( m n l o g ( m n ) ) O(mnlog(mn)) O(mnlog(mn)),其它操作的时间复杂度均比排序小。
- 空间复杂度: O ( m n ) O(mn) O(mn),需要存储的节点数量为m × \times ×n。