1D 接雨水问题(也叫 Trapping Rain Water)是一个经典的动态规划和栈的应用题。其目标是计算给定一系列柱子的高度值,能够接多少雨水。
问题描述
给定一个整数数组 height[],表示柱子的高度,数组的每个元素 height[i] 表示第 iii 个柱子的高度。雨水会在两柱子之间的空隙中积水,且积水的量由两柱子的最小高度和它们之间的宽度决定。计算并返回该数组所能接的雨水总量。
思路
1D 接雨水问题的关键点是确定每个位置能接的雨水量。具体来说,某个位置能够存储的水量,取决于该位置左边和右边的最大高度。
核心思想
-
当前位置接水的高度:对于任意位置 iii,它接的水量取决于当前位置两边的最高柱子。设:
left_max[i]:从左边到位置 iii 的最大高度。right_max[i]:从右边到位置 iii 的最大高度。
那么,在位置 iii 能接的水量是:
water_trapped[i]=max(0,min(left_max[i],right_max[i])−height[i])
这个式子的含义是:当前位置 iii 能接的水量是左边和右边的最大高度的较小值减去当前位置的高度。
-
步骤:
- 第一步:构建
left_max数组,记录每个位置左侧最高的柱子。 - 第二步:构建
right_max数组,记录每个位置右侧最高的柱子。 - 第三步:遍历每个位置,计算当前位置的接水量。
- 第一步:构建
时间复杂度
- 构建
left_max数组:O(n)O(n)O(n) - 构建
right_max数组:O(n)O(n)O(n) - 计算接水量:O(n)O(n)O(n)
总体时间复杂度是 O(n)O(n)O(n),其中 nnn 是柱子的数量。
代码实现
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int trap(vector<int>& height) {
int n = height.size();
if (n == 0) return 0;
// 创建 left_max 和 right_max 数组
vector<int> left_max(n), right_max(n);
// 填充 left_max 数组
left_max[0] = height[0];
for (int i = 1; i < n; ++i) {
left_max[i] = max(left_max[i - 1], height[i]);
}
// 填充 right_max 数组
right_max[n - 1] = height[n - 1];
for (int i = n - 2; i >= 0; --i) {
right_max[i] = max(right_max[i + 1], height[i]);
}
// 计算接水量
int water = 0;
for (int i = 0; i < n; ++i) {
water += min(left_max[i], right_max[i]) - height[i];
}
return water;
}
int main() {
vector<int> height = {0,1,0,2,1,0,1,3,2,1,2,1};
cout << "Total water trapped: " << trap(height) << endl;
return 0;
}
优化:空间复杂度优化
上面的解法需要额外的两个数组 left_max 和 right_max,空间复杂度为 O(n)O(n)O(n)。我们可以通过双指针的方式来优化空间复杂度,将其优化到 O(1)O(1)O(1)。
空间优化思路
- 使用两个指针
left和right分别指向数组的两端,计算左侧最大值和右侧最大值。 - 使用变量
left_max和right_max来存储当前的最大高度。 - 根据当前的高度和最大值计算每个位置的接水量。
优化后代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int trap(vector<int>& height) {
int n = height.size();
if (n == 0) return 0;
int left = 0, right = n - 1;
int left_max = 0, right_max = 0;
int water = 0;
while (left <= right) {
if (height[left] <= height[right]) {
if (height[left] >= left_max) {
left_max = height[left];
} else {
water += left_max - height[left];
}
left++;
} else {
if (height[right] >= right_max) {
right_max = height[right];
} else {
water += right_max - height[right];
}
right--;
}
}
return water;
}
int main() {
vector<int> height = {0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1};
cout << "Total water trapped: " << trap(height) << endl;
return 0;
}
总结
- 1D 接雨水问题的核心是计算每个位置能接的水量。
- 动态规划通过预计算每个位置的左侧最大值和右侧最大值,能够高效求解。
- 优化空间复杂度:使用双指针可以将空间复杂度优化为 O(1)O(1)O(1),并且时间复杂度保持为 O(n)O(n)O(n)。
通过以上方法,可以非常高效地解决接雨水问题,适用于各种输入规模。