42. 接雨水
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
/**
* L42. 接雨水
* 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
* n == height.length
* 1 <= n <= 2 * 104
* 0 <= height[i] <= 105
*
* @param height
* @return
*/
public int trap(int[] height) {
//0,1,0,2,1,0,1,3,2,1,2,1
//思路其实是不太好想出来的,建议直接题解,题解告诉我们将问题拆分
//拆分为求每个柱子可以接的雨水的数量
//那每个柱子可以接的水量怎么求呢?当前柱子左右两边的最高柱子较低值 - 当前柱子的高度
// (当前柱子如果高于左右两边最高柱子的较低值,那么这个柱子无法接雨水)
// 1. 先求每个柱子左右两边的最高值
// 2. for循环计算即可
int n = height.length;
//至少需要三根柱子
if (n < 3) {
return 0;
}
int[] left = new int[n];
int[] right = new int[n];
for (int i = 1; i < n; i++) {
left[i] = Math.max(left[i - 1], height[i - 1]);
}
for (int i = n - 2; i >= 0; i--) {
right[i] = Math.max(right[i + 1], height[i + 1]);
}
int sum = 0;
//头和尾都没有办法接雨水
for (int i = 1; i < n - 1; i++) {
int min = Math.min(left[i], right[i]);
sum += min > height[i] ? min - height[i] : 0;
}
return sum;
}
407. 接雨水 II
给你一个 m x n 的矩阵,其中的值均为非负整数,代表二维高度图每个单元的高度,请计算图中形状最多能接多少体积的雨水。
/**
* 407. 接雨水 II
* <p>
* 给你一个 m x n 的矩阵,其中的值均为非负整数,代表二维高度图每个单元的高度,请计算图中形状最多能接多少体积的雨水。
*
* @param heightMap
* @return
*/
public int trapRainWater(int[][] heightMap) {
//错误方式
//同1类似,也求每个列可以盛水的数量。
// 要求前后左右四个维度的最低值,如果大于当前高度,即min - height[i][j]即可
int m = heightMap.length;
int n = heightMap[0].length;
int[][] left = new int[m][n];
int[][] right = new int[m][n];
int[][] up = new int[m][n];
int[][] down = new int[m][n];
for (int i = 0; i < m; i++) {
for (int j = 1; j < n; j++) {
left[i][j] = Math.max(left[i][j - 1], heightMap[i][j - 1]);
}
for (int j = n - 2; j >= 0; j--) {
right[i][j] = Math.max(right[i][j + 1], heightMap[i][j + 1]);
}
}
for (int j = 0; j < n; j++) {
for (int i = 1; i < m; i++) {
up[i][j] = Math.max(up[i - 1][j], heightMap[i - 1][j]);
}
for (int i = m - 2; i >= 0; i--) {
down[i][j] = Math.max(down[i + 1][j], heightMap[i + 1][j]);
}
}
int sum = 0;
for (int i = 1; i < m - 1; i++) {
for (int j = 1; j < n - 1; j++) {
int min = Math.min(Math.min(left[i][j], right[i][j]), Math.min(up[i][j], down[i][j]));
sum += min > heightMap[i][j] ? min - heightMap[i][j] : 0;
}
}
return sum;
}
开始的思路很简单,就是从一维推到二维,解决方式是一样的,求前后左右四个维度的最低值,如果大于当前高度,即min - height。但是提交代码之后啪啪打脸,并不能通过。然后去看了题解,但是并没有能理解为什么一维推到二维的方式 是行不通的,题解里也没有给出,最后是在一个题解中分析出来的 以图中标蓝的2为例,它上下左右的最大值分别为:7,8,12,12,以一维的思路,他能接的雨水的最大值就是7 - 2 = 5;那我们再往上看5,旁边4是小于5的,那么5肯定是不能接雨水的。那么问题就来了,2接了雨水高度到了7但是5不能接雨水,7的雨水就没办法存储,会留到5,接着就沿着左侧的4流出去了。二维的复杂度在于它不仅受到上下左右高度的影响,也受到斜角的影响
现在我们来看正确的题解思路,大的思路是围城,然后从最小的城墙往内推进。遇到比最小的还低的墙,那么这个地方就是可以接水的。比较抽象,建议看这个题解:合围垒砖块:路我已经堵死了里面的动图,很清晰(比官方题解好懂多了)
下面就是解题代码:
public int trapRainWaterV2(int[][] heightMap) {
int m = heightMap.length;
int n = heightMap[0].length;
boolean[][] visited = new boolean[m][n];
//数组记录位置i,j和height
PriorityQueue<int[]> queue = new PriorityQueue<>(Comparator.comparingInt(o -> o[2]));
//将数组四周的元素先添加到优先级队列
for (int i = 0; i < m; i++) {
queue.offer(new int[]{i, 0, heightMap[i][0]});
queue.offer(new int[]{i, n - 1, heightMap[i][n - 1]});
visited[i][0] = true;
visited[i][n - 1] = true;
}
for (int j = 1; j < n - 1; j++) {
queue.offer(new int[]{0, j, heightMap[0][j]});
queue.offer(new int[]{m - 1, j, heightMap[m - 1][j]});
visited[0][j] = true;
visited[m - 1][j] = true;
}
int result = 0;
int[][] dir = new int[][]{{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
while (!queue.isEmpty()) {
//每次取最小元素,表示当前围墙的最低高度
int[] cur = queue.poll();
for (int[] ints : dir) {
int newX = cur[0] + ints[0];
int newY = cur[1] + ints[1];
//向四周扩散
if (newX >= 0 && newX < m && newY >= 0 && newY < n && !visited[newX][newY]) {
//新元素高度比当前围墙最低高度还要低,那么这快是可以接住雨水的
if (cur[2] > heightMap[newX][newY]) {
result += cur[2] - heightMap[newX][newY];
}
//围墙往里缩
queue.offer(new int[]{newX, newY, Math.max(cur[2], heightMap[newX][newY])});
visited[newX][newY] = true;
}
}
}
return result;
}
收工!