42. 接雨水(困难)

1,问题描述

42. 接雨水

难度:困难

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

示例 1:

img

1
2
3
输入: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:

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

提示:

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

2,思考过程

​ 我自己最开始想到的是双指针解决问题,接水取决于短板在哪里,所以优先动短板即可

​ 在看了官方的题解后,发现动态规划解法、单调栈解法

3,代码处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import java.util.Deque;
import java.util.LinkedList;
import java.util.Stack;

public class _42接雨水 {


// 还是双边指针问题
public int trap(int[] height) {
int left = 0;
int heighLeft = 0;

int right = height.length - 1;
int heighRight = 0;

int container = 0;
while (left <= right) {
if (height[left] <= height[right]) {
if (heighLeft <= height[left]) {
heighLeft = height[left];
} else {
container += (heighLeft - height[left]);
}
left++;
} else {
if (heighRight <= height[right]) {
heighRight = height[right];
} else {
container += (heighRight - height[right]);
}
right--;
}
}
return container;
}

// 单调堆栈解法
public int trap_gov_monotonic_stack(int[] height) {
int ans = 0;
Deque<Integer> stack = new LinkedList<Integer>();
int n = height.length;
for (int i = 0; i < n; ++i) {
while (!stack.isEmpty() && height[i] > height[stack.peek()]) {
int top = stack.pop();
if (stack.isEmpty()) {
break;
}
int left = stack.peek();
int currWidth = i - left - 1;
int currHeight = Math.min(height[left], height[i]) - height[top];
ans += currWidth * currHeight;
}
stack.push(i);
}
return ans;
}


// 官方的动态规划
public int trap_gov_dynamic(int[] height) {
int[] leftMax = new int[height.length];
int[] rightMax = new int[height.length];

for (int i = 1; i < height.length; i++) {
leftMax[i] = Math.max(height[i], height[i - 1]);
leftMax[i] = Math.max(leftMax[i], leftMax[i - 1]);
}

for (int i = height.length - 2; i >= 0; i--) {
rightMax[i] = Math.max(height[i], height[i + 1]);
rightMax[i] = Math.max(rightMax[i], rightMax[i + 1]);
}

int container = 0;
for (int i = 0; i < height.length; i++) {
int min = Math.min(leftMax[i], rightMax[i]);
if (min > height[i]) {
container += min - height[i];
}
}
return container;
}

// 官方的双指针
public int trap_gov_double_index(int[] height) {
int ans = 0;
int left = 0, right = height.length - 1;
int leftMax = 0, rightMax = 0;
while (left < right) {
leftMax = Math.max(leftMax, height[left]);
rightMax = Math.max(rightMax, height[right]);
if (height[left] < height[right]) {
ans += leftMax - height[left];
++left;
} else {
ans += rightMax - height[right];
--right;
}
}
return ans;
}

public static void main(String[] args) {
_42接雨水 trap = new _42接雨水();
System.out.println(trap.trap_gov_monotonic_stack(new int[]{4,1,1,6}));
}
}


// 5,5,0,1,4,6,5
// 1,2,6,6,2,1

4,思考过程

动态规划解法:

​ 1️⃣一个坑中,可以存水的多少取决于左右边界的最小值 Min(left、right)-heigh[i]>0时成立

​ 2️⃣直接找到左边界、右边界的最大值数组即可

单调栈解法(需要review加深印象):

​ 1️⃣假设右边可能有一个很高的挡板,我们暂时只考虑左边的低挡板就行

​ 2️⃣如果这个坑的左边界高,那么表示可以积水,否则不能积水会流走

​ 3️⃣当前这个坑比左边边界高时,那么计算一波前面积攒的水(一直计算到无法进行积水为止)