文章目录
- 1、单调栈接雨水的过程
- 2、算法正确性的关键点:
- 3、简化理解:
- 4、算法的实现
题目路径
:
42.接雨水
其他解法
:
接雨水(动态规划/双指针/贪心)
单调栈原理
:
单调栈和单调队列
单调栈直接用于找每个元素"左右侧"第一个更高的元素,并不直接给出左右两侧最高的元素。不过,在接雨水问题中,单调栈的应用逻辑稍有不同,它的核心在于利用栈来识别和计算每个凹形区间(即可以积水的区间)。一旦识别出来就计算其能积水的量,然后进行填充。填充后,你可以认为柱形变高了。
1、单调栈接雨水的过程
栈中永远是递减序列,一旦遇到凹型区域就砌平。
当我们在遍历数组时,遇到一个当前遍历的柱子高度比栈顶元素的高度大的情况,这意味着我们找到了一个潜在的凹形区域的右边界。这个凹形区域的左边界是栈中的次栈顶元素。此时,栈顶元素是凹形区域的底部,而当前柱子成为了凹形区域的右边界。
凹形区域的宽度是(j - i - 1)
,其中i
是次栈顶元素的位置,j
是当前柱子的位置。凹形区域可以填充的水的高度是由i
和j
位置的柱子的较矮者决定的,减去凹底的高度,即min(height[i], height[j]) - height[mid]
,这里mid
是凹底,即栈顶元素的位置。因此,这个凹形区域可以积累的水量是(min(height[i], height[j]) - height[mid]) * (j - i - 1)
。
这个计算过程会一直重复进行,直到栈为空或者当前遍历的柱子的高度不再大于栈顶元素的高度。通过这种方式,我们可以确保计算出数组中所有可能的凹形区域中积累的水。
实际上,这个计算过程不是简单地填充每个凹形区域之间的每一列水,而是根据凹形区域的左右边界高度和凹底高度计算出该区域能够积累的水量。这种方法在一旦有凹型区域时就把该凹型区域填充起来,使得每次虽然看起来像是只填充一列但实际上是每次都把柱子砌平,使得每次填充实际上是在整个中间是平坦水柱构成凹型区域上进行填充
。
中间是遍历过程中被砌平的。
因此,单调递减栈在这个问题中的应用确保了每次计算的是有效的凹形区域,并且能够正确地计算出每个区域能够积累的水量,从而得到整个数组中能够积累的总水量。这种方法的有效性和效率使得它成为解决接雨水问题的强大工具。
2、算法正确性的关键点:
-
局部积水量的计算:单调栈用于确定每个凹形区间,并计算这个区间内的积水量。对于栈中每个元素,它的左侧边界是栈中之前的元素,右侧边界是当前考察的元素。这个“左边界-凹底-右边界”构成了一个可以积水的凹形区间。
-
凹形区间的最高边界:虽然单调栈直接找到的是左右侧第一个更高的元素,但是解决接雨水问题可以认为每次找到一个凹型区间,则将该区间填充,虽然本次可能不能考虑到整个的最高边界,但是
其他水柱的计算方法仍然会使其填充到最高
。 -
迭代构建:通过迭代的方式,栈帮助我们一步步向右“扫描”数组,每次遇到能形成新的凹形区间的元素时,就计算该区间的积水量。这种方式确保了即便是在多个嵌套的凹形区间中,每个区间的积水量也能被准确计算。
3、简化理解:
- 当我们遍历到新的高度时,如果这个高度小于栈顶元素对应的高度,它就被推入栈中,意味着可能会有一个新的凹形区间开始形成。
- 如果这个高度大于栈顶元素对应的高度,那么栈顶元素(凹底)和栈中之前的元素(左边界)、当前元素(右边界)一起形成一个凹形区间。我们可以立即计算出这个区间内的积水量,然后移除栈顶元素。
- 这个过程重复进行,直到当前元素小于新的栈顶元素(意味着不能形成新的凹形区间)或栈为空(意味着左边没有更高的边界了)。
4、算法的实现
class Solution {
public:
int trap(vector<int>& height) {
int ans = 0;
stack<int> stk;
int n = height.size();
for (int i = 0; i < n; ++i) {
while (!stk.empty() && height[i] > height[stk.top()]) {
int top = stk.top();
stk.pop();
if (stk.empty()) {
break;
}
int left = stk.top();
int currWidth = i - left - 1;
int currHeight = min(height[left], height[i]) - height[top];
ans += currWidth * currHeight;
}
stk.push(i);
}
return ans;
}
};