精选专栏链接 🔗
欢迎订阅,点赞+关注,每日精进1%,与百万开发者共攀技术珠峰
更多内容持续更新中~
【LeetCode 热题 100】接雨水
📝题目描述
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例1

bash
输入: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:
bash
输入:height = [4,2,0,3,2,5]
输出:9
💡提示信息
- n == height.length;
- 1 <= n <= 2 ∗ 10 4 2 * 10^4 2∗104;
- 0 <= heighti <= 10 5 10^5 105;
什么是木桶效应?
"木桶效应"(Cask Effect),也被称为"短板效应"或"木桶定律",是一个经典的管理学和系统论概念。简单来说,它的核心含义是:一只木桶能盛多少水,并不取决于最长的那块木板,而是取决于最短的那块木板。
这个理论由美国管理学家劳伦斯·J·彼得(Laurence J. Peter)提出,用以说明一个系统或组织的整体效能,往往受制于其最薄弱的环节。
解题思路解析
这道题的核心在于:对于数组中的任意一个位置
i,它能接到的雨水高度是多少?
根据木桶效应,位置 i 上方的水位高度,取决于它左侧最大高度 和右侧最大高度 中的较小值。
公式如下:
water i = max ( 0 , min ( left_max i , right_max i ) − height i ) \text{water}i = \max(0, \min(\text{left\_max}i, \text{right\_max}i) - \text{height}i) wateri=max(0,min(left_maxi,right_maxi)−heighti)
left_max[i]:位置i左边(包含i)最高的柱子高度;right_max[i]:位置i右边(包含i)最高的柱子高度;height[i]:当前位置柱子的高度;- 如果计算结果小于 0,说明当前柱子高度比水位还高,接不到水,记为 0;
基于这个核心思路,有两种主要的实现方式。
解法一:动态规划 (预处理数组)
这是最直观的解法。我们可以预先计算每个位置的 left_max 和 right_max 并存储在数组中,这样在遍历时就可以 O ( 1 ) O(1) O(1) 时间获取。
算法步骤:
- 创建两个数组
leftMax和rightMax; - 从左向右扫描,填充
leftMax:leftMax[i] = max(leftMax[i-1], height[i]); - 从右向左扫描,填充
rightMax:rightMax[i] = max(rightMax[i+1], height[i]); - 最后遍历一次数组,累加每个位置的雨水量;
Java 代码实现
java
class Solution {
public int trap(int[] height) {
if (height == null || height.length == 0) {
return 0;
}
int n = height.length;
int[] leftMax = new int[n];
int[] rightMax = new int[n];
// 1. 填充左边的最大值
leftMax[0] = height[0];
for (int i = 1; i < n; i++) {
leftMax[i] = Math.max(leftMax[i - 1], height[i]);
}
// 2. 填充右边的最大值
rightMax[n - 1] = height[n - 1];
for (int i = n - 2; i >= 0; i--) {
rightMax[i] = Math.max(rightMax[i + 1], height[i]);
}
// 3. 计算总雨水量
int ans = 0;
for (int i = 0; i < n; i++) {
int waterLevel = Math.min(leftMax[i], rightMax[i]) - height[i];
ans += waterLevel ;
}
return ans;
}
}
提交代码,运行结果如下

复杂度分析
时间复杂度: O ( N ) O(N) O(N),我们需要遍历三次数组,每次都是 O ( N ) O(N) O(N)的时间复杂度;
空间复杂度: O ( N ) O(N) O(N),我们需要两个额外的数组来存储最大高度。
解法二:双指针 (最优解)
方法一虽然快,但占用 O ( N ) O(N) O(N) 的额外空间。我们可以通过双指针 技巧将空间复杂度优化到 O ( 1 ) O(1) O(1)。
算法思路:
我们维护两个指针 left 和 right,分别从数组的两端向中间移动。同时维护两个变量 left_max 和 right_max,分别记录 left 指针左边扫过的最大值和 right 指针右边扫过的最大值。
关键逻辑:
依然根据公式:
water i = max ( 0 , min ( left_max i , right_max i ) − height i ) \text{water}i = \max(0, \min(\text{left\_max}i, \text{right\_max}i) - \text{height}i) wateri=max(0,min(left_maxi,right_maxi)−heighti)
在移动过程中,我们总是处理较矮那一侧的指针。
- 如果
height[left] < height[right]:- 由于算法每次都只移动较矮的一侧,能走到现在这个 left,说明左边从来没有被右边更高的柱子挡住而停滞不前;
- 这意味着左边目前的最大值
left_max一定小于右边的某个值(这个值可能是当前的height[right]或者right_max); - 根据木桶效应,当前位置
left的水位高度完全由left_max决定(因为短板在左边); - 我们可以直接计算
left位置的雨水,然后将left右移;
- 反之,如果
height[left] >= height[right]:- 同理,右边目前的最大值
right_max是短板。 - 我们可以直接计算
right位置的雨水,然后将right左移。
- 同理,右边目前的最大值
关健难点理解: 为什么
height[left] < height[right]时,左边目前的最大值left_max一定小于右边的某个值?
双指针每次只移动较矮的一侧 。假设我们成功地将左指针从起始位置 0 一路移动到了当前位置 left,这意味着在之前的每一步中,每当左指针指向某个位置时,它都满足"比当前右指针矮"的条件,从而被允许向右移动。如果曾经出现过"左指针指向的柱子比右指针高"的情况,那么算法就会去移动右指针,而左指针就会停在原地等待。
所以,左指针从未因为自己比右边高而被卡住;它每次都是因为自己比右边矮才得以向前移动。换句话说,右边始终有一个不低于左指针当前高度的墙(右指针位置)存在,才使得左指针能够不断前进。
当 height[left] < height[right] 成立时,左指针能走到当前位置,意味着在之前的每一步中,左指针指向的柱子始终比当时的右指针矮。
Java 代码实习如下:
java
class Solution {
public int trap(int[] height) {
// 校验
if (height == null || height.length == 0) {
return 0;
}
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 (height[left] < height[right]) {
// 左边矮,处理左边
// 此时 leftMax 一定小于 rightMax,所以水位由 leftMax 决定
ans += leftMax - height[left];
left++;
} else {
// 右边矮(或相等),处理右边
// 此时 rightMax 一定小于等于 leftMax,水位由 rightMax 决定
ans += rightMax - height[right];
right--;
}
}
return ans;
}
}
提交代码,运行结果如下:

复杂度分析
时间复杂度: O ( N ) O(N) O(N),双指针遍历一次数组;
空间复杂度: O ( 1 ) O(1) O(1),只需要常数级别的额外空间;
总结
| 方法 | 时间复杂度 | 空间复杂度 | 评价 |
|---|---|---|---|
| 动态规划 | O ( N ) O(N) O(N) | O ( N ) O(N) O(N) | 逻辑简单,容易想到,适合面试时作为第一解法。 |
| 双指针 | O ( N ) O(N) O(N) | O ( 1 ) O(1) O(1) | 最优解,对思维要求较高,体现了对"木桶效应"的深刻理解。 |
希望这篇解析能帮你彻底搞懂"接雨水"问题!