LeetCode第42题:https://leetcode-cn.com/problems/trapping-rain-water/
- 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
- 输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
- 输出:6
- 解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
思路:首先,我们先明确可以接多少雨水是怎么计算出来的?有横竖两种方向可以看。如下图所示:
横向:
纵向:
这里以纵向为例:
第0列可以接得的雨水量为:0 (因为左边没有柱子比第0列的柱子高)
第1列可以接得的雨水量为:0 (因为左边没有柱子比第1列的柱子高)
第2列可以接得的雨水量为:1 (左边第1列的柱子比第2列高,右边第3列的柱子比第2列的柱子高,于 是第1,2,3列形成宽为1高也为1的凹槽)
第3列可以接得的雨水量为:0 (因为左边没有柱子比第3列的柱子高)
对于第4、5、6列,很明显能形成凹槽,于是我们可以按列分割这个凹槽如下:
第4列可以接得的雨水量为:1
第5列可以接得的雨水量为:2
第6列可以接得的雨水量为:1
第7列可以接得的雨水量为:0 (因为左边没有柱子比第7列的柱子高, 右边没有柱子比第7列的柱子高)
第8列可以接得的雨水量为:0 (右边没有柱子比第8列的柱子高)
第9列可以接得的雨水量为:1
第10列可以接得的雨水量为:0 (右边没有柱子比第10列的柱子高)
第11列可以接得的雨水量为:0 (因为右边没有柱子比第11列的柱子高)
从上面我们可以找到一些规律:
- 我们以列为单位去分割总任务,最后要求总的接水量,其实就是把每一列的接水量加起来的总和。
- 要想要能接水,那么左边的柱子高度必须严格大于该列的高度,右边的柱子也必须严格大于该列高度(即:左右与该列要能形成凹槽)。
我们可以使用单调栈。
- 确定栈的顺序。
我们首先要确定栈内元素是递增还是递减。根据上面两个规律,我们可以得出栈内元素是递减(从栈底到栈顶)。 - 确定存放内容
这里存放元素下标比较方便,因为我们需要计算凹槽宽高,而元素我们可以直接使用下标访问得到。 - 处理逻辑
此时栈内是递减的,下一个将要进站的元素是下标为6的元素(高度为1),如果它直接进栈就会破坏单调栈,因此,需要元素出栈,那么下标为5的元素就要出栈,在它出栈的同时,我们可以直接计算出这个出栈的元素可以收集的雨水量,但是有一个注意点是,在这里每个小凹槽按行收集比按列收集要方便。
首先,我们要记得我们维护的栈是一个非递增的栈。也就是说只有将要进栈的元素严格大于栈顶元素我们不会让其进栈。假设将要进栈的元素比栈顶元素大,那么我们会选择栈顶元素出栈,计算其接水量。换句话说,这个将要进栈的元素就成为了右挡板
将要进栈的元素为第7号,此时栈顶是第6号元素,将要进栈元素比栈顶元素大,那么第7号元素就成为了第6号元素的右挡板,此时我们要找第6号的左挡板,其左挡板一定是在栈内元素中的。此时,栈内元素一定是大于等于第6号元素的。我们可以假设出栈后的栈顶是其左挡板,那么其接水量就为((将要进栈元素下标 - 出栈后栈顶元素下标) - 1) ×(min(左挡板, 右挡板) - 出栈元素)。即((7-4)-1×( min(1, 3) - 1) = 0。也就是说4号元素作为左挡板不能接水(必定是因为左挡板和中间高度相同,这是由于单调栈的特性决定的),那么我们继续出栈,这过程也就是找寻左挡板的过程。如上面这幅图所示,我们找到第3号元素就是其左挡板,可以计算出其接水量。
总结
- 我们其实在总体遍历的过程中是按列遍历的,然后不断找到可以接水的凹槽,对于每个凹槽我们其实是按行的方式去计算出其盛水量的,我们在出栈的过程中其实是不断找寻左挡板的过程,这过程中会遇见接不了水的情况(我们也可以认为接水量为0,依旧把他加入最后结果中)。
- 代码
int trap(vector<int>& height) {
stack<int> st; //单调栈内存放元素下标
st.push(0); //放入第一个元素
int sum = 0;
for (int i = 1; i < height.size(); ++i) {
if (st.empty() || height[i] <= height[st.top()]) {
st.push(i);
}
else {
while (!st.empty() && height[i] > height[st.top()]) {
int index = st.top(); //栈顶元素出栈并记录下标
st.pop();
if (!st.empty()) {
int w = i - st.top() - 1; //凹槽宽
int h = min(height[i], height[st.top()]) - height[index]; //凹槽高
sum += w * h; //累加所有接水量
}
}
}
st.push(i);
}
return sum;
}