642 words
3 minutes
接雨水

Cover Image Source: Source

题目链接:42. 接雨水

思路#

本题思路有动态规划、单调栈、双指针等,详见官方解答。这里提供一种动态规划和单调栈结合的方法。

总体思路#

雨水面积可表示为

Srain=(Srain+Spillar)Spillar=StotalSpillar.S_{rain} = (S_{rain} + S_{pillar}) - S_{pillar} = S_{total} - S_{pillar}.

SpillarS_{pillar}直接求和可以得出,因此关键在于如何求出StotalS_{total}。下面通过动态规划解决这个问题。

动态规划#

dp[i]dp[i] 表示仅存在下标在 [0,i][0, i] 之中的柱子时,柱子和雨水的面积之和。考虑添加第 i+1 号柱子。如果 [0,i][0, i] 中存在比第 i+1 号柱子更高的柱子,假设第 front[i+1]front[i+1] 号柱子是这些柱子中最靠后的柱子,则

dp[i+1]=dp[front[i+1]]+(i+1front[i+1])height[i+1].dp[i + 1] = dp[front[i+1]] + (i + 1 - front[i+1])height[i + 1].

如果不存在,则找到 [0,i][0, i] 中最高的柱子,假设是第 ki+1k_{i+1} 号柱子,则

dp[i+1]=dp[ki+1]+(iki+1)height[ki+1]+height[i+1].dp[i + 1] = dp[k_{i+1}] + (i - k_{i+1})height[k_{i+1}] + height[i + 1].

为了区分以上两种情况,用 front[i]=ifront[i] = i 表示第 i 号柱子之前不存在更高的柱子。

由此可以得出状态转移方程:

dp[i]={dp[i]=dp[front[i]]+(ifront[i])height[],front[i]i,dp[i]=dp[ki]+(iki1)height[ki]+height[i],otherwise.dp[i] = \left\{ \begin{aligned} & dp[i] = dp[front[i]] + (i - front[i])height[], & front[i] \neq i, \\ & dp[i] = dp[k_i] + (i - k_i - 1)height[k_i] + height[i], & otherwise. \end{aligned} \right.

现在最大的问题是如何求出 front[i]front[i] 。这里使用单调栈来求解。

单调栈#

从右往左将数列中的元素加入单调递减栈,当单调性不满足时,将栈顶元素弹出直至满足单调性。每轮循环中,对于任意被弹出的元素 a ,新加入的元素 b 即为所有在 a 左侧且比 a 大的元素中距离 a 最近的元素。由此可求得 front[]front[\cdot] 数组。

代码#

代码如下:

class Solution {
public:
int trap(vector<int> &height) {
int n = height.size();
stack<int> mono; // monotone stack
vector<int> front(height.size(), 0);
vector<int> dp(height.size() + 1, 0);
int sum = 0; // 高度之和
int maxIndex = 0; // 当前高度最高的柱子
// 单调栈
for (int i = n - 1; i >= 0; --i) {
sum += height[i];
front[i] = i;
while (!mono.empty() && height[mono.top()] < height[i]) {
front[mono.top()] = i;
mono.pop();
}
mono.push(i);
}
dp[0] = height[0];
for (int i = 1; i < n; ++i) {
if (front[i] < i)
dp[i] = dp[front[i]] + height[i] * (i - front[i]);
else // front[i] == i
dp[i] = dp[maxIndex] + height[maxIndex] * (i - maxIndex - 1) + height[i];
maxIndex = height[i] > height[maxIndex] ? i : maxIndex;
}
return dp[n - 1] - sum;
}
};

评测结果#

执行用时:16 ms, 在所有 C++ 提交中击败了54.90%的用户

内存消耗:20.6 MB, 在所有 C++ 提交中击败了5.04%的用户

通过测试用例:322 / 322