这题是经典中的经典,核心就一句话:
某个位置能接多少水,取决于它左边最高柱子 和 右边最高柱子 中较矮的那个。
因为水会从低的那边流走。
比如:
height = [4,2,0,3,2,5]
看 0 这个位置:
4 2 0 3 2 5
^
它左边最高是 4
右边最高是 5
能装水高度:
min(4,5)=4
当前位置高度是 0
所以这里能接:
4 - 0 = 4
一、暴力思路(帮助理解)
对于每个位置:
-
找左边最大值
-
找右边最大值
-
算当前能接多少水
公式:
water[i] = min(leftMax,rightMax) - height[i]
但这样每个位置都遍历一次左右:
- 时间复杂度 O(n²)
太慢。
二、前缀最大值优化(推荐先理解)
我们提前把:
-
每个位置左边最高
-
每个位置右边最高
都算出来。
1. leftMax数组
leftMax[i] 表示:
i位置左边(包含自己)的最高柱子
例如:
height = [4,2,0,3,2,5]
leftMax:
[4,4,4,4,4,5]
因为:
-
到2位置时,左边最高还是4
-
到3位置时,最高还是4
-
到5位置时,变成5
2. rightMax数组
rightMax[i]
表示i位置右边(包含自己)的最高柱子
结果:
[5,5,5,5,5,5]
3. 计算每个位置接水量
公式:
min(leftMax[i], rightMax[i]) - height[i]
三、完整代码(最容易理解)
java
class Solution {
public int trap(int[] height) {
int n = height.length;
if (n == 0) return 0;
int[] leftMax = new int[n];
int[] rightMax = new int[n];
// 计算左边最大值
leftMax[0] = height[0];
for (int i = 1; i < n; i++) {
leftMax[i] = Math.max(leftMax[i - 1], height[i]);
}
// 计算右边最大值
rightMax[n - 1] = height[n - 1];
for (int i = n - 2; i >= 0; i--) {
rightMax[i] = Math.max(rightMax[i + 1], height[i]);
}
// 计算雨水
int ans = 0;
for (int i = 0; i < n; i++) {
ans += Math.min(leftMax[i], rightMax[i]) - height[i];
}
return ans;
}
}
四、为什么公式一定成立?
因为:
当前位置最终水位:
由左右两边更矮的墙决定
例如:
5 2
虽然左边5很高,
但右边只有2,
水到了2就流走了。
所以水位只能是:
min(5,2)=2
这是整题最核心的思想。
五、双指针(面试最优解)
空间还能优化。
因为我们其实不需要整个数组。
只需要:
-
当前左边最大值
-
当前右边最大值
即可。
核心思想(非常重要)
如果:
leftMax < rightMax
那么:
当前位置能接多少水,
已经只由 leftMax 决定了。
因为右边一定至少有个更高的。
所以:
water = leftMax - height[left]
然后移动左指针。
反之移动右指针。
六、双指针代码(重点)
class Solution {
public int trap(int[] height) {
int left = 0;
int right = height.length - 1;
int leftMax = 0;
int rightMax = 0;
int ans = 0;
while (left < right) {
leftMax = Math.max(leftMax, height[left]);
rightMax = Math.max(rightMax, height[right]);
// 哪边小处理哪边
if (leftMax < rightMax) {
ans += leftMax - height[left];
left++;
} else {
ans += rightMax - height[right];
right--;
}
}
return ans;
}
}
七、为什么"双指针只看矮的一边"是对的?
这是这题最难理解的地方。
比如:
leftMax = 3
rightMax = 5
当前位置左边水位:
最终一定不会超过3。
因为短板是左边。
右边已经够高了。
所以:
当前位置接水量
= 3 - 当前高度
右边具体是不是5、8、100,
已经不重要。
所以可以直接结算左边。
八、这题本质是什么?
本质是:
找"当前位置左右两边的边界"
类似:
-
木桶短板
-
区间最小限制
-
左右夹逼
这是很多困难题的核心思想。
这题建议你按这个顺序掌握:
-
暴力法(理解)
-
前后缀最大值(标准解)
-
双指针(真正掌握)