42. 接雨水

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

示例 1:
接雨水 - 图1
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。

示例 2:
输入:height = [4,2,0,3,2,5]
输出:9


提示:

  • n == height.length
  • 1 <= n <= 2 * 104
  • 0 <= height[i] <= 105


思路:

  1. 单调栈:考虑每一个可行高度(由每个元素的高度决定)和宽度

维护一个单调递减栈,遍历到当前元素时如果大于栈顶元素说明这两者之间构成了一个凹槽,可以用来蓄水

  1. 双指针:考虑每一个位置能存水的最大高度

最大高度取决于左右两边最大高度的最小值。
左右分别维护一个最大值,根据当前左右最大值判断左指针移动还是右指针移动(木桶效应)。

  1. //单调栈
  2. class Solution {
  3. public int trap(int[] height) {
  4. Deque<Integer> q = new ArrayDeque<>();
  5. int ans = 0;
  6. for (int i = 0; i < height.length; i++) {
  7. int last = 0;
  8. while (!q.isEmpty() && height[q.peekLast()] <= height[i]) {
  9. int j = q.pollLast();
  10. ans += (height[j] - last) * (i - j - 1);
  11. last = height[j];
  12. }
  13. //如果此时栈中还有元素说明左边高度此时大于右边高度,需要用右边元素来更新
  14. if (!q.isEmpty())
  15. ans += (height[i] - last) * (i - q.peekLast() - 1);
  16. q.offer(i);
  17. }
  18. return ans;
  19. }
  20. }
  1. //双指针
  2. class Solution {
  3. public int trap(int[] height) {
  4. int n = height.length;
  5. int lmax = height[0], rmax = height[n - 1];
  6. int l = 0, r = n - 1;
  7. int ans = 0;
  8. while (l <= r) {
  9. lmax = Math.max(lmax, height[l]);
  10. rmax = Math.max(rmax, height[r]);
  11. if (lmax < rmax) {
  12. ans += Math.max(0, lmax - height[l]);
  13. l++;
  14. } else {
  15. ans += Math.max(0, rmax - height[r]);
  16. r--;
  17. }
  18. }
  19. return ans;
  20. }
  21. }

407. 接雨水 II

给你一个 m x n 的矩阵,其中的值均为非负整数,代表二维高度图每个单元的高度,请计算图中形状最多能接多少体积的雨水。

示例 1:
接雨水 - 图2
输入: heightMap = [[1,4,3,1,3,2],[3,2,1,3,2,4],[2,3,3,2,3,1]]
输出: 4
解释: 下雨后,雨水将会被上图蓝色的方块中。总的接雨水量为1+2+1=4。

示例 2:
接雨水 - 图3
输入: heightMap = [[3,3,3,3,3],[3,2,2,2,3],[3,2,1,2,3],[3,2,2,2,3],[3,3,3,3,3]]
输出: 10


提示:

  • m == heightMap.length
  • n == heightMap[i].length
  • 1 <= m, n <= 200
  • 0 <= heightMap[i][j] <= 2 * 104

思路:
类似于二维的接雨水的双指针做法。
一个位置能存的最大高度等价于在这个位置倒水,直至水流出边界的最大高度
从流出倒推!类似于Dijkstra但不相同。

如果能流出一定是从当前外围一圈的最低高度处流出。

  1. class Solution {
  2. public int trapRainWater(int[][] h) {
  3. if (h.length == 0 || h[0].length == 0)
  4. return 0;
  5. int n = h.length, m = h[0].length;
  6. PriorityQueue<Node> q = new PriorityQueue<>((o1, o2) -> o1.val - o2.val);
  7. boolean[][] st = new boolean[n][m];
  8. for (int i = 0; i < n; i++) {
  9. st[i][0] = st[i][m - 1] = true;
  10. q.offer(new Node(i, 0, h[i][0]));
  11. q.offer(new Node(i, m - 1, h[i][m - 1]));
  12. }
  13. for (int j = 1; j + 1 < m; j++) {
  14. st[0][j] = st[n - 1][j] = true;
  15. q.offer(new Node(0, j, h[0][j]));
  16. q.offer(new Node(n - 1, j, h[n - 1][j]));
  17. }
  18. int res = 0;
  19. int[] dx = {-1, 0, 1, 0}, dy = {0, 1, 0, -1};
  20. while (!q.isEmpty()) {
  21. Node p = q.poll();
  22. int x = p.x, y = p.y, val = p.val;
  23. res += val - h[x][y];
  24. for (int i = 0; i < 4; i++) {
  25. int a = x + dx[i], b = y + dy[i];
  26. if (a >= 0 && a < n && b >= 0 && b < m && !st[a][b]) {
  27. st[a][b] = true;
  28. q.offer(new Node(a, b, Math.max(h[a][b], val)));
  29. }
  30. }
  31. }
  32. return res;
  33. }
  34. }
  35. class Node {
  36. int x, y, val;
  37. Node (int x, int y, int val) {
  38. this.x = x;
  39. this.y = y;
  40. this.val = val;
  41. }
  42. }

方法二:并查集
从低向高依次处理每个位置,假设当前正在处理的位置为cur,考虑其四周,若已经被处理过,将cur并入。
考虑cur是否为边界,若是,将其合并到0所在集合,并计算此连通块能存的水量。

  1. class Solution {
  2. int n, m;
  3. int[] fa;
  4. int[] size;
  5. int[] sum;
  6. int ans;
  7. public int trapRainWater(int[][] heightMap) {
  8. //填块法(并查集)
  9. n = heightMap.length;
  10. m = heightMap[0].length;
  11. fa = new int[n * m + 1];
  12. size = new int[n * m + 1];
  13. sum = new int[n * m + 1];
  14. int[][] a = new int[n * m + 1][3];
  15. a[0] = new int[]{0, 0, 0};
  16. for (int i = 0; i < n; i++) {
  17. for (int j = 0; j < m; j++) {
  18. int idx = getIdx(i, j);
  19. a[idx] = new int[]{heightMap[i][j], i, j};
  20. }
  21. }
  22. Arrays.sort(a, (o1, o2) -> (o1[0] == o2[0] ? getIdx(o1[1], o1[2]) - getIdx(o2[1], o2[2]) : o1[0] - o2[0]));
  23. int[] dy = {0, 1, 0, -1}, dx = {-1, 0, 1, 0};
  24. // for (int i = 0; i <= n * m; i++)
  25. // System.out.println(Arrays.toString(a[i]));
  26. for (int i = 1; i <= n * m; i++) {
  27. int idx = getIdx(a[i][1], a[i][2]);
  28. fa[idx] = idx;
  29. size[idx] = 1;
  30. sum[idx] = a[i][0];
  31. for (int j = 0; j < 4; j++) {
  32. int x = a[i][1] + dx[j], y = a[i][2] + dy[j];
  33. if (x >= 0 && x < n && y >= 0 && y < m && size[getIdx(x, y)] > 0)
  34. merge(idx, getIdx(x, y), a[i][0]);
  35. }
  36. if (a[i][1] == 0 || a[i][1] == n - 1 || a[i][2] == 0 || a[i][2] == m - 1)
  37. merge(0, idx, a[i][0]);
  38. }
  39. return ans;
  40. }
  41. int find(int x) {
  42. return x == fa[x] ? x : (fa[x] = find(fa[x]));
  43. }
  44. void merge(int x, int y, int h) {
  45. int px = find(x), py = find(y);
  46. if (px == py) return;
  47. if (px > py) {
  48. int t = px;
  49. px = py;
  50. py = t;
  51. }
  52. fa[py] = px;
  53. size[px] += size[py];
  54. sum[px] += sum[py];
  55. if (px == 0)
  56. ans += (h * size[py] - sum[py]);
  57. }
  58. int getIdx(int i, int j) {
  59. return i * m + j + 1;
  60. }
  61. }

关键在在于处理cur时,将其并入四周连通块的集合 并查集做法