题目地址:
https://leetcode.com/problems/trapping-rain-water/
题目大意是,给定一个长
n
n
n数组
A
A
A,表示每个位置的“柱子”高度,求下雨时这些柱子能盛放的水的体积。
法1:双指针。容易知道,每个位置 i i i的正上方能盛的水的量取决于其左右最高的两个柱子的较矮者,如果这个较矮者的高度为 h h h,则 A [ i ] A[i] A[i]正上方能盛的水的量就等于 max { 0 , h − A [ i ] } \max\{0,h-A[i]\} max{0,h−A[i]}。于是我们可以从最左和最右开两个指针,同时开两个变量记录左边最高柱子高度和右边最高柱子高度,初始化为 A A A的首尾数字,然后从两边到中间依次计算每个位置的水量。具体做法请看代码注释,代码如下:
class Solution {
public:
int trap(vector<int>& h) {
int res = 0;
// maxl表示当前位置的左边的最高柱子高度,maxr表示当前位置的右边的最高柱子高度
for (int l = 0, r = h.size() - 1, maxl = h[0], maxr = h.back(); l <= r;)
// 如果maxl比maxr小,说明A[l + 1]的位置的左边最高就是maxl,右边最高就是maxr,则可以算出其盛水量。
// 算完盛水量以后更新一下maxL
if (maxl <= maxr)
res += max(0, maxl - h[l]), maxl = max(maxl, h[l]), l++;
else
res += max(0, maxr - h[r]), maxr = max(maxr, h[r]), r--;
return res;
}
};
时间复杂度 O ( n ) O(n) O(n),空间 O ( 1 ) O(1) O(1)。
法2:动态规划。容易看出来,下标为 i i i的位置的正上方能盛多少水,取决于其左右两边最高柱子的较小值减去 A [ i ] A[i] A[i]。因为下雨的时候,它左右最高柱子之间盛放的水的高度,就是两个柱子的较矮者。所以我们只需要求出每个位置的左右两边的最大值即可。设 f [ i ] f[i] f[i]是 A [ i ] A[i] A[i]的左边的最大值,那么 f [ i ] = max { f [ i − 1 ] , A [ i − 1 ] } f[i]=\max\{f[i-1],A[i-1]\} f[i]=max{f[i−1],A[i−1]}其中 f [ 0 ] = A [ 0 ] f[0]=A[0] f[0]=A[0]。 A [ i ] A[i] A[i]的右边的最大值也有类似的递推公式。最后重新遍历数组,求盛放水的体积即可。代码如下:
class Solution {
public:
int trap(vector<int>& h) {
int n = h.size();
int fl[n], fr[n];
fl[0] = h[0];
for (int i = 1; i < n; i++) fl[i] = max(fl[i - 1], h[i - 1]);
fr[n - 1] = h[n - 1];
for (int i = n - 2; i >= 0; i--) fr[i] = max(fr[i + 1], h[i + 1]);
int res = 0;
for (int i = 1; i < n - 1; i++) res += max(0, min(fl[i], fr[i]) - h[i]);
return res;
}
};
时空复杂度 O ( n ) O(n) O(n)。
法2:单调栈。与上面做法不同的是,我们这里可以考虑横着切(这里类似于勒贝格积分的做法,而上面的做法是类似黎曼积分的做法)。考虑某个柱子与其两边最近的比它高的柱子所围成的水的量(比如图中的
2
,
3
,
4
2,3,4
2,3,4组成的矩形),设某个位置
i
i
i处,两边离它最近且比它高的柱子分别是
A
[
l
]
A[l]
A[l]和
A
[
r
]
A[r]
A[r],那么这个矩形的长就是
r
−
l
−
1
r-l-1
r−l−1,而宽就是
min
{
A
[
l
]
,
A
[
r
]
}
−
A
[
i
]
\min\{A[l],A[r]\}-A[i]
min{A[l],A[r]}−A[i]。我们可以开一个严格单调下降栈,遇到比栈顶大于等于的数的时候,就将栈顶出栈,累加一下矩形面积。代码如下:
class Solution {
public:
int trap(vector<int>& h) {
int n = h.size();
int res = 0;
stack<int> stk;
for (int i = 0; i < n; i++) {
while (stk.size() && h[stk.top()] <= h[i]) {
int tp = stk.top(); stk.pop();
if (stk.size())
res += (min(h[stk.top()], h[i]) - h[tp]) * (i - stk.top() - 1);
}
stk.push(i);
}
return res;
}
};
时空复杂度 O ( n ) O(n) O(n)。