Q:給定 n 個非負整數表示每個寬度為 1 的柱子的高度圖,計算按此排列的柱子,下雨之后能接多少雨水。
上面是由數組 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度圖,在這種情況下,可以接 6 個單位的雨水(藍色部分表示雨水)。 感謝 Marcos 貢獻此圖。
示例:
輸入: [0,1,0,2,1,0,1,3,2,1,2,1]
輸出: 6
A:
(引用自《labuladong的算法小抄》)
位置 i 能達到的⽔柱⾼度和其左邊的最⾼柱⼦、右邊的最⾼柱⼦有關,我們分別稱這兩個柱⼦⾼度為 l_max和 r_max ;位置 i 最⼤的⽔柱⾼度就是 min(l_max, r_max) 。
用一個備忘錄記錄一下:
public int trap(int[] height) {
if (height.length <= 2)
return 0;
int n = height.length;
int[] l_max = new int[n];
l_max[0] = height[0];
int[] r_max = new int[n];
r_max[n - 1] = height[n - 1];
//左側最高
for (int i = 1; i < n; i++) {
l_max[i] = Math.max(l_max[i - 1], height[i]);
}
//右側最高
for (int i = n - 2; i >= 0; i--) {
r_max[i] = Math.max(r_max[i + 1], height[i]);
}
int sum = 0;
for (int i = 1; i < n - 1; i++) {
sum += Math.min(l_max[i], r_max[i]) - height[i];
}
return sum;
}
使用雙指針法:
public int trap(int[] height) {
if (height.length <= 2)
return 0;
int n = height.length;
int left = 0, right = n - 1;
int l_max = height[0], r_max = height[n - 1];
int sum = 0;
while (left <= right) {
l_max = Math.max(l_max, height[left]);
r_max = Math.max(r_max, height[right]);
if (l_max > r_max) {
sum += r_max - height[right];
right--;
} else {
sum += l_max - height[left];
left++;
}
}
return sum;
}
很容易理解, l_max 是 height[0..left] 中最⾼柱⼦的⾼度, r_max 是 height[right..end] 的最⾼柱⼦的⾼度。此時的 l_max 是 left 指針左邊的最⾼柱⼦,但是 r_max 並不⼀定是left 指針右邊最⾼的柱⼦,這真的可以得到正確答案嗎?
其實這個問題要這么思考,我們只在乎 min(l_max, r_max) 。對於上圖的情況,我們已經知道 l_max < r_max 了,⾄於這個 r_max 是不是右邊最⼤的,不重要,重要的是 height[i] 能夠裝的⽔只和 l_max 有關。
Q:給你一個 m x n 的矩陣,其中的值均為正整數,代表二維高度圖每個單元的高度,請計算圖中形狀最多能接多少體積的雨水。
示例:
給出如下 3x6 的高度圖:
[
[1,4,3,1,3,2],
[3,2,1,3,2,4],
[2,3,3,2,3,1]
]
返回 4 。
A:引用:https://blog.csdn.net/qq_29996285/article/details/86696301
思路:
- 搜索隊列使用優先級隊列(STL的優先隊列的底層實現默認是大頂堆),越低的點優先級越高(構造小頂堆),優先級越高的點越優先搜索。
- 以矩陣四周的點作為起始點進行廣度優先搜索(這些點在搜索前需要push進隊列中)。
- 使用一個二維數組對push進隊列的點進行標記,如果該點被標記過,則不會被push到隊列中。
- 只要優先級隊列不空,即去除優先級隊列的隊頭元素進行搜索,按照上下左右四個方向進行擴展,擴展過程中忽略超出邊界的點和已經入隊的點。
- 當某個點 (x, y, h) 進行拓展時,得到的新點 (newx, newy) 的高度 height 小於 h,則——積水高度+=h-height;,並更新 h 的值為 height 。
- 將(newx, newy, height)入隊,並做上標記。
舉例:
將四周的點添加至優先隊列Q中,並做上標記
開始進行擴展:
藍色代表正在搜索的節點,綠色代表隊中的節點,紫色代表已完成節點,紅色代表擴展的新節點(優先級隊列中的元素:優先隊列<行,列,高>)
代碼:
public int trapRainWater(int[][] heightMap) {
if (heightMap.length <= 2 || heightMap[0].length <= 2)
return 0;
int n = heightMap.length;
int m = heightMap[0].length;
boolean[][] visit = new boolean[n][m];
PriorityQueue<int[]> pq = new PriorityQueue<>();
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (i == 0 || i == n - 1 || j == 0 || j == m - 1) {
pq.offer(new int[]{i, j, heightMap[i][j]});
visit[i][j] = true;
}
}
}
int res = 0;
int[] dirs = {-1, 0, 1, 0, -1};
while (!pq.isEmpty()) {
//小跟堆的最小高度
int[] poll = pq.poll();
for (int k = 0; k < 4; k++) {
int newx = poll[0] + dirs[k];
int newy = poll[1] + dirs[k + 1];
if (newx >= 0 && newy >= 0 && newx < n && newy < m && !visit[newx][newy]) {
//比當前這個最小高度還小,說明里面可以裝水
if (poll[2] > heightMap[newx][newy])
res += (poll[2] - heightMap[newx][newy]);
//放入堆
pq.offer(new int[]{newx, newy, Math.max(heightMap[newx][newy], poll[2])});
visit[newx][newy] = true;
}
}
}
return res;
}