积水面积问题是一个经典的算法题目,主要考察对数据结构的理解和应用能力。题目描述了一组由正方体叠起的柱子,要求计算这些柱子之间能够积水的总面积。这个问题在实际生活中有很多应用场景,比如城市排水系统设计、地形蓄水计算等。
想象一排高低不齐的柱子排列在一起,下雨时雨水会积聚在柱子之间的凹陷处。积水的形成需要满足一个基本条件:两侧都必须有比当前位置更高的柱子,这样才能形成"容器"来盛装雨水。
举个例子,给定柱子高度序列:[0,1,0,2,1,2,0,0,2,0],我们可以这样理解:
从数学角度看,对于每个位置i,它能积存的水量由以下因素决定:
积水量计算公式为:
water[i] = min(left_max, right_max) - height[i]
如果结果为正,则计入总量;否则不计入。
左右最大高度法是最直观的解决方案,其核心思想是:
这种方法的时间复杂度是O(n),因为需要三次遍历:一次计算左最大,一次计算右最大,一次计算结果。空间复杂度也是O(n),需要存储左右最大高度数组。
cpp复制#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
int n;
cin >> n;
vector<int> height(n);
for (int i = 0; i < n; ++i) {
cin >> height[i];
}
if (n < 3) { // 边界条件处理
cout << 0 << endl;
return 0;
}
// 计算左侧最大高度
vector<int> left_max(n, 0);
for (int i = 1; i < n; ++i) {
left_max[i] = max(left_max[i-1], height[i-1]);
}
// 计算右侧最大高度
vector<int> right_max(n, 0);
for (int i = n-2; i >= 0; --i) {
right_max[i] = max(right_max[i+1], height[i+1]);
}
// 计算总积水量
int total = 0;
for (int i = 0; i < n; ++i) {
int water = min(left_max[i], right_max[i]) - height[i];
if (water > 0) {
total += water;
}
}
cout << total << endl;
return 0;
}
虽然这种方法已经很高效,但我们还可以做一些优化:
单调栈是一种特殊的栈结构,它保持栈内元素按照某种顺序(递增或递减)排列。在积水面积问题中,我们使用单调递减栈,即栈底到栈顶的元素对应的高度是递减的。
当遇到一个比栈顶高的柱子时,说明可能形成了一个可以积水的凹槽。这时我们弹出栈顶作为凹槽底部,新的栈顶是左边界,当前柱子是右边界,计算这两者之间的积水量。
cpp复制#include <iostream>
#include <vector>
#include <stack>
using namespace std;
int main() {
int n;
cin >> n;
vector<int> height(n);
for (int i = 0; i < n; ++i) {
cin >> height[i];
}
stack<int> st; // 存储柱子索引,高度单调递减
int total = 0;
for (int i = 0; i < n; ++i) {
while (!st.empty() && height[i] > height[st.top()]) {
int bottom_idx = st.top();
st.pop();
if (st.empty()) break;
int left_idx = st.top();
int h = min(height[left_idx], height[i]) - height[bottom_idx];
int w = i - left_idx - 1;
total += h * w;
}
st.push(i);
}
cout << total << endl;
return 0;
}
以输入[0,1,0,2,1,2,0,0,2,0]为例,单调栈法的执行过程如下:
两种方法的时间复杂度都是O(n),但实际运行时间有差异:
对于随机数据,单调栈法通常更快;对于极端数据(如完全递减序列),单调栈法退化为O(n²)。
在信息学竞赛中,输入输出有严格规范:
标准模板:
cpp复制#include <bits/stdc++.h>
using namespace std;
int main() {
freopen("test.in","r",stdin);
freopen("test.out","w",stdout);
// 解题代码
fclose(stdin);
fclose(stdout);
return 0;
}
在实际编程中,需要特别注意以下边界条件:
推荐验证方法:
调试技巧:
将问题扩展到三维空间,计算三维地形中的积水体积。这类问题通常需要使用优先队列或更复杂的数据结构,是二维问题的自然延伸。
考虑柱子高度会随时间变化的情况,需要设计支持动态更新和快速查询的数据结构,如线段树或树状数组。
这些问题都可以使用类似的单调栈或双指针技术解决,体现了算法思想的通用性。
在实际解决这个问题的过程中,我总结了以下几点经验:
理解问题本质是关键。积水问题的核心在于找到每个位置的"容器边界",这个洞察直接引出了两种解法。
从暴力法开始思考是个好习惯。最直观的暴力法是对于每个位置,向左右扫描找最大值,时间复杂度O(n²)。优化这个思路就得到了左右最大高度法。
单调栈的应用需要多练习。虽然单调栈的解法更高效,但理解起来有一定难度。建议多画图模拟栈的变化过程。
边界条件容易出错。特别是单调栈法中,当栈弹出后为空时,说明没有左边界,不能形成积水区域。
性能优化要适度。对于竞赛来说,O(n)的解法已经足够,不必过度优化。但在工程实践中,可能需要考虑更极致的优化。
测试用例要全面。除了题目给的样例,还应该测试极端情况、随机生成数据,确保代码的鲁棒性。
可视化帮助理解。画出柱子高度和积水区域的示意图,可以直观地验证算法的正确性。