1、单调栈
考虑到接雨水的特点,我们实质上能够接住雨水的是这样一个部分:在前半部分这个柱子的高度都是递减的,直到最后一个柱子 的高度超过了前半部分中最后一个柱子的高度,这个时候我们计算被超过的柱子上到其两侧较矮柱子的水量,并以此方法遍历整个数组。
基于以上计算水量的方法,我们可以考虑使用单调栈进行处理。虽然栈是一个先进后出的结构,但我们可以通过比较栈顶元素和下一位元素之间的大小关系,栈顶元素小则将其出栈直至栈顶元素大于下一位元素,然后再将下一位元素进栈,这样我们就可以确保栈中的数字都是递增或递减的。因此单调栈只处理Next Greater Element问题,即给你一个数组,返回一个等长的数组,对应索引存储着下一个更大元素,如果没有更大的元素,就存 -1。
基于以上的分析,我们可以设计代码如下:首先从数组开始位置进行遍历,若此时单调栈不为空且栈顶元素的高度小于下一个位置的高度则说明此时出现了一个高于栈中最后一个柱子的新柱子,因此需要计算此时最后一个柱子上方的水量。首先记录此时栈顶元素的下标并出栈,若出栈之后栈为空,说明此时左侧不再有柱子肯定接不了水,因此跳出循环。否则计算出栈元素的高度并与两侧柱子高度中的较小值进行比较计算这一层的水量。循环结束之后将新元素入栈并重复上述操作。
class Solution {
public:
int trap(vector<int> &height) {
int ans = 0;
stack<int> st;
for (int i = 0; i < height.size(); i++)
{
while (!st.empty() && height[st.top()] < height[i])
{
int cur = st.top();
st.pop();
if (st.empty()) break;
int l = st.top();
int r = i;
int h = min(height[r], height[l]) - height[cur];
ans += (r - l - 1) * h;
}
st.push(i);
}
return ans;
}
};
2、动态规划法
显然,leftMax[0]=height[0],leftMax[0]=height[0],rightMax[n-1]=height[n-1],rightMax[n−1]=height[n−1]。两个数组的其余元素的计算如下:
当 1 <= i <= n−1 时,leftMax[i]=max(eftMax[i-1],height[i]);
当 0 <= i <= n-2 时,rightMax[i]=max(rightMx[i+1], height[i])。
class Solution {
public int trap(int[] height) {
int n = height.length;
if (n == 0) {
return 0;
}
int[] leftMax = new int[n];
leftMax[0] = height[0];
for (int i = 1; i < n; ++i) {
leftMax[i] = Math.max(leftMax[i - 1], height[i]);
}
int[] rightMax = new int[n];
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;
}
}
3、双指针
双指针法是在动态规划法的基础上进行改进,从而降低了空间复杂度。注意到下标 i 处能接的雨水量由 leftMax[i] 和 rightMax[i] 中的最小值决定。数组 leftMax 是从左往右计算,数组 rightMax 是从右往左计算。
1、当两个指针未相遇时,移动left和right更新 leftMax 和 rightMax 的值;
2、如果 height[left] < height[right],则必有 leftMax < rightMax,下标 left 处能接的雨水量等于 leftMax - height[left],将下标 left 处能接的雨水量加到能接的雨水总量,然后将 \textit{left}left 加 11(即向右移动一位;
3、如果 height[left] >= height[right],则必有 leftMax >= rightMax,下标 right 处能接的雨水量等于 rightMax - height[right],将下标 right 处能接的雨水量加到能接的雨水总量,然后将 right 减 1(即向左移动一位。
class Solution {
public:
int trap(vector<int>& height) {
int ans = 0;
int left = 0, right = height.size() - 1;
int leftMax = 0, rightMax = 0;
while (left < right) {
leftMax = max(leftMax, height[left]);
rightMax = max(rightMax, height[right]);
if (height[left] < height[right]) {
ans += leftMax - height[left];
++left;
} else {
ans += rightMax - height[right];
--right;
}
}
return ans;
}
};