LeetCodeAnimation/0407-Trapping-Rain-Water-II/Article/0407-Trapping-Rain-Water-II.md
2020-04-25 12:51:35 -07:00

127 lines
4.7 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# LeetCode 407 号问题接雨水 II
> 本文首发于公众号图解面试算法 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客https://www.algomooc.com
题目来源于 LeetCode 上第 407 号问题接雨水 II题目难度为 Hard目前通过率为 38%
### 题目描述
给你一个 m x n 的矩阵其中的值均为正整数代表二维高度图每个单元的高度请计算图中形状最多能接多少体积的雨水
**示例:**
```
给出如下 3x6 的高度图:
[
[1,4,3,1,3,2],
[3,2,1,3,2,4],
[2,3,3,2,3,1]
]
返回 4
```
![](../Animation/example.png)
### 题目解析
1 2 维的矩阵中每个格子都有其高度问这个 2 维矩阵能够盛多少的水首先我们分析格子能够盛水的必要条件是其周围存在格子比当前格子高这样水才能够被框得住但是仔细一想最外围的格子怎么办它们是存不了水的可以把最外围的格子想象成围栏它们的作用就是保证里面格子的水不会流出来所以我们就得先考虑这些格子它们的高度直接决定了内部格子的蓄水量但是这些格子也有局部性一个格子的长短并不会影响矩阵当中所有的格子但是它会影响与其相邻的格子那么我们就需要有一个考虑的顺序那就是优先考虑最外层最短的格子由于每个格子都会影响到其周围的格子内部格子也需要列入考虑范围每次我们都考虑最短的格子然后看其周围有没有没考虑过的比它还短的格子于是就有了考虑的先后顺序
1. 考虑最外层格子
2. 选出最外层最短的格子
3. 考虑该格子与其相邻的内部格子是否能盛水并把这个内部格子也纳入考虑范围
4. 在考虑范围内的所有格子中选出最短的格子重复步骤 3
这里需要注意的是每次纳入考虑范围的格子是加了水之后的高度而不是之前的高度原因想一下应该不难理解另外就是可以使用了 这个数据结构来帮助实现寻找 当前考虑范围内最短的格子 这个操作步骤
### 动画描述
![](../Animation/407.gif)
### 代码实现
```java
private class Pair {
int x, y, h;
Pair(int x, int y, int h) {
this.x = x;
this.y = y;
this.h = h;
}
}
private int[] dirX = {0, 0, -1, 1};
private int[] dirY = {-1, 1, 0, 0};
public int trapRainWater(int[][] heightMap) {
if (heightMap.length == 0 || heightMap[0].length == 0) {
return 0;
}
int m = heightMap.length;
int n = heightMap[0].length;
PriorityQueue<Pair> pq = new PriorityQueue<>(new Comparator<Pair>() {
@Override
public int compare(Pair a, Pair b) {
return a.h - b.h;
}
});
boolean[][] visited = new boolean[m][n];
// 优先将外围的元素加入队列中
for (int i = 0; i < n; ++i) {
pq.offer(new Pair(0, i, heightMap[0][i]));
pq.offer(new Pair(m - 1, i, heightMap[m - 1][i]));
visited[0][i] = true;
visited[m - 1][i] = true;
}
for (int i = 1; i < m - 1; ++i) {
pq.offer(new Pair(i, 0, heightMap[i][0]));
pq.offer(new Pair(i, n - 1, heightMap[i][n - 1]));
visited[i][0] = true;
visited[i][n - 1] = true;
}
int result = 0;
while (!pq.isEmpty()) {
Pair cur = pq.poll();
// 遍历当前位置上下左右四个方向
for (int k = 0; k < 4; ++k) {
int curX = cur.x + dirX[k];
int curY = cur.y + dirY[k];
if (curX < 0 || curY < 0 || curX >= m || curY >= n || visited[curX][curY]) {
continue;
}
if (heightMap[curX][curY] < cur.h) {
result += cur.h - heightMap[curX][curY];
}
pq.offer(new Pair(curX, curY,
Math.max(heightMap[curX][curY], cur.h)));
visited[curX][curY] = true;
}
}
return result;
}
```
<br>
### 复杂度分析
因为使用了优先队列这个数据结构每次元素出入队列的时间复杂度是 O(logn)于是我们可以得出整体时间复杂度是 `O(m*n*logm*n)`当然需要说明的是这是最差时间复杂度由于并不是所有的元素都一次性加入队列平均时间复杂度要比这个来的低具体是什么就得看输入数据了空间复杂度是 `O(m*n)`这里也不难理解通过这道题堆的用法又被很好地展现了出来
![](../../Pictures/qrcode.jpg)