单调栈的使用一:接雨水


题目路径42.接雨水
其他解法接雨水(动态规划/双指针/贪心)
单调栈原理单调栈和单调队列
在这里插入图片描述

  单调栈直接用于找每个元素"左右侧"第一个更高的元素,并不直接给出左右两侧最高的元素。不过,在接雨水问题中,单调栈的应用逻辑稍有不同,它的核心在于利用栈来识别和计算每个凹形区间(即可以积水的区间)。一旦识别出来就计算其能积水的量,然后进行填充。填充后,你可以认为柱形变高了。
在这里插入图片描述

1、单调栈接雨水的过程

在这里插入图片描述栈中永远是递减序列,一旦遇到凹型区域就砌平。

  当我们在遍历数组时,遇到一个当前遍历的柱子高度比栈顶元素的高度大的情况,这意味着我们找到了一个潜在的凹形区域的右边界。这个凹形区域的左边界是栈中的次栈顶元素。此时,栈顶元素是凹形区域的底部,而当前柱子成为了凹形区域的右边界。
  凹形区域的宽度是(j - i - 1),其中i是次栈顶元素的位置,j是当前柱子的位置。凹形区域可以填充的水的高度是由ij位置的柱子的较矮者决定的,减去凹底的高度,即min(height[i], height[j]) - height[mid],这里mid是凹底,即栈顶元素的位置。因此,这个凹形区域可以积累的水量是(min(height[i], height[j]) - height[mid]) * (j - i - 1)
  这个计算过程会一直重复进行,直到栈为空或者当前遍历的柱子的高度不再大于栈顶元素的高度。通过这种方式,我们可以确保计算出数组中所有可能的凹形区域中积累的水。
  实际上,这个计算过程不是简单地填充每个凹形区域之间的每一列水,而是根据凹形区域的左右边界高度和凹底高度计算出该区域能够积累的水量。这种方法在一旦有凹型区域时就把该凹型区域填充起来,使得每次虽然看起来像是只填充一列但实际上是每次都把柱子砌平,使得每次填充实际上是在整个中间是平坦水柱构成凹型区域上进行填充
在这里插入图片描述中间是遍历过程中被砌平的。

  因此,单调递减栈在这个问题中的应用确保了每次计算的是有效的凹形区域,并且能够正确地计算出每个区域能够积累的水量,从而得到整个数组中能够积累的总水量。这种方法的有效性和效率使得它成为解决接雨水问题的强大工具。

2、算法正确性的关键点:

  1. 局部积水量的计算:单调栈用于确定每个凹形区间,并计算这个区间内的积水量。对于栈中每个元素,它的左侧边界是栈中之前的元素,右侧边界是当前考察的元素。这个“左边界-凹底-右边界”构成了一个可以积水的凹形区间。

  2. 凹形区间的最高边界:虽然单调栈直接找到的是左右侧第一个更高的元素,但是解决接雨水问题可以认为每次找到一个凹型区间,则将该区间填充,虽然本次可能不能考虑到整个的最高边界,但是其他水柱的计算方法仍然会使其填充到最高

  3. 迭代构建:通过迭代的方式,栈帮助我们一步步向右“扫描”数组,每次遇到能形成新的凹形区间的元素时,就计算该区间的积水量。这种方式确保了即便是在多个嵌套的凹形区间中,每个区间的积水量也能被准确计算。

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;
    }
};
题目描述: 给定n个非负整数表示每个宽度为1的柱子的高度图,计算下雨后能够捕获的雨水量。 示例: 输入:[0,1,0,2,1,0,1,3,2,1,2,1] 输出:6 解释:如图所示,这是雨水的容器。 思路: 1. 定义个栈stack,用来存储柱子的下标。定义个变量ans,用来存储到的雨水量。定义个变量cur,用来存储当前的下标。 2. 遍历数组,如果stack为空或者当前柱子高度小于等于栈顶柱子高度,把当前下标入栈。 3. 如果当前柱子高度大于栈顶柱子高度,说明形成了个凹槽,可以雨水。此时需要弹出栈顶元素,计算当前柱子和新的栈顶柱子之间的距离和高度差,用来计算到的雨水量。 4. 如果当前柱子高度大于栈顶柱子高度,重复第3步,直到当前柱子高度小于等于栈顶柱子高度或者栈为空。 5. 遍历结束后,栈中剩余的柱子没有形成凹槽,无法雨水,将栈中剩余的柱子依次出栈,计算到的雨水量。 代码实现: C++实现: class Solution { public: int trap(vector<int>& height) { stack<int> s; int ans = 0; for (int i = 0; i < height.size(); i++) { while (!s.empty() && height[i] > height[s.top()]) { int cur = s.top(); s.pop(); if (s.empty()) break; int left = s.top(); int right = i; int h = min(height[left], height[right]) - height[cur]; ans += h * (right - left - 1); } s.push(i); } return ans; } }; Python实现: class Solution: def trap(self, height: List[int]) -> int: s = [] ans = 0 for i in range(len(height)): while s and height[i] > height[s[-1]]: cur = s[-1] s.pop() if not s: break left = s[-1] right = i h = min(height[left], height[right]) - height[cur] ans += h * (right - left - 1) s.append(i) return ans
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yorelee.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值