【LeetCode】1631.最小體力消耗路徑(Path WIth Minimum Effort)
技術標籤:LeetCode演算法題演算法leetcode圖論資料結構
目錄
題目描述
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)
A route’s effort is the maximum absolute difference in heights between two consecutive cells of the route.
Return the minimum effort
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。