单调栈的应用——接雨水

题目描述
LeetCode 42. 接雨水(难度:困难)
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
在这里插入图片描述

解题思路:

  1. 首先,拿到这道题目的时候,我们应该先想,什么情况下才能接到雨水?如果凭空想象会很难,但是结合题目给出的图解就非常清晰了,如果后面的柱子高度比前面的柱子高度小,那么是接不到雨水的,而如果后面的柱子高度是大于前面柱子高度的,那么是有可能接到雨水的(注意只是有可能,后面解题过程中我们就可以看出接不到雨水的情形),想到这一步,就可以把这道题转换一下思路,变成求解在某个数之后,找到第一个比它大的数,这个时候我们就应该想到单调栈
  2. 回忆一下单调栈的应用场景:求在某个数之后,第一个比本身小的数,可以用单调递增栈(因为要出栈说明当前元素比栈顶元素小);求在某个数之后,第一个比本身大的数,可以用单调递减栈(因为要出栈说明当前元素比栈顶元素大)
  3. 因此,这里我们用单调递减栈来作为解题工具,维护一个单调递减栈,从左往右顺序遍历柱子高度数组,如果当前柱子高度小于栈顶柱子高度,符合单调栈性质,直接入栈,如果当前柱子高度小于栈顶柱子高度,违反单调栈性质,需要弹出栈中的元素直至符合条件,当前元素才能入栈
  4. 每当有元素出栈的时候,说明找到比当前栈顶元素柱子高度更高的了,有机会接到雨水,那么又是哪种情形可以接到雨水,哪种情形接不到雨水?答案就是:当有一个元素出栈之后,此时栈不为空,那么可以接到雨水;而如果此时栈变为空,那么接不到雨水(因为构不成一个封闭空间,这里不理解的建议动手画一画图)
  5. 如果确定能够接收到雨水,那么如何计算接收到的雨水数量呢?当栈顶元素出栈之后,该元素的值就是相当于水平线,需要保存起来,水坑的高度就是当前元素和新栈顶元素的最小值减去刚刚出栈的元素值(相当于要减去一个水平线的参考值);而水坑的宽度就是当前元素和新栈顶元素之间的水平距离(可以用数组的索引求出),最终用宽度乘以高度就是本次接收到的雨水数量
  6. 注意:当有元素出栈之后,计算了此次接收到的雨水数量,如果栈不为空,并且当前元素的值仍然大于新栈顶元素的值,那么重复同样的操作,再次出栈,直到当前元素可以入栈为止

为了更好的理解这道题目,我再以题目给出的示例详细的列出每一个步骤(虽然有点啰嗦^^)

解题步骤
PS:下面步骤中给出的栈中元素从左到右表示栈底到栈顶

  1. 当前遍历到的元素为0,此时栈空,直接入栈(栈中元素:0)
  2. 当前遍历到的元素为1,1>栈顶元素0,不符合单调栈性质,因此栈顶元素0出栈,此时栈变为空,那么接不到雨水,因为要能接到雨水,需要当前元素和新栈顶元素围成一个空间,此时1入栈(栈中元素:1)
  3. 当前遍历到的元素为0,0<栈顶元素1,符合单调栈性质,直接入栈(栈中元素:1,0)
  4. 当前遍历到的元素为2,2>栈顶元素0,不符合单调栈性质,因此栈顶元素0出栈,此时栈不为空,可以接到雨水,底部参考值为0,当前元素和新栈顶元素1之间的水平距离为(3-1-1=1),水坑的高度为当前元素和新栈顶元素1二者值小的那个减去底部参考值,即为(1-0=1),总雨水量等于宽度乘以高度(1x1=1);当前栈顶元素1仍小于2,继续出栈,此时栈为空,接不到雨水,那么2入栈(栈中元素:2)
  5. 当前遍历到的元素为1,1<栈顶元素2,符合单调栈性质,直接入栈(栈中元素:2,1)
  6. 当前遍历到的元素为0,0<栈顶元素1,符合单调栈性质,直接入栈(栈中元素:2,1,0)
  7. 当前遍历到的元素为1,1>栈顶元素0,不符合单调栈性质,因此栈顶元素0出栈,此时栈不为空,可以接到雨水,底部参考值为0,当前元素和新栈顶元素1之间的水平距离为(6-4-1=1),水坑的高度为当前元素和新栈顶元素1二者值小的那个减去底部参考值,即为(1-0=1),总雨水量等于宽度乘以高度(1x1=1);当前栈顶元素1等于1,也不符合单调栈性质,继续出栈,此时栈不为空,可以接到雨水,底部参考值为1,当前元素和新栈顶元素2之间的水平距离为(6-3-1=2),水坑的高度为当前元素和新栈顶元素2二者值小的那个减去底部参考值,即为(1-1=0),总雨水量等于宽度乘以高度(1x0=0);此时我们可以得出结论:当前元素值等于栈顶元素值,也是接不到雨水的;此时栈顶元素2>1,1可以入栈(栈中元素:2,1)
  8. 当前遍历到的元素为3,3>栈顶元素1,不符合单调栈性质,因此栈顶元素1出栈,此时栈不为空,可以接到雨水,底部参考值为1,当前元素和新栈顶元素2之间的水平距离为(7-3-1=3),水坑的高度为当前元素和新栈顶元素2二者值小的那个减去底部参考值,即为(2-1=1),总雨水量等于宽度乘以高度(3x1=3);当前栈顶元素2仍小于3,继续出栈,此时栈为空,接不到雨水,那么3入栈(栈中元素:3)
  9. 当前遍历到的元素为2,2<栈顶元素3,符合单调栈性质,直接入栈(栈中元素:3,2)
  10. 当前遍历到的元素为1,1<栈顶元素2,符合单调栈性质,直接入栈(栈中元素:3,2,1)
  11. 当前遍历到的元素为2,2>栈顶元素1,不符合单调栈性质,因此栈顶元素1出栈,此时栈不为空,可以接到雨水,底部参考值为1,当前元素和新栈顶元素2之间的水平距离为(10-8-1=1),水坑的高度为当前元素和新栈顶元素2二者值小的那个减去底部参考值,即为(2-1=1),总雨水量等于宽度乘以高度(1x1=1);当前栈顶元素2等于2,不符合单调栈性质,2出栈,由前面结论可知是接不到雨水的,当前栈顶元素3大于2,符合单调栈性质,2入栈(栈中元素:3,2)
  12. 当前遍历到的元素为1,1<栈顶元素2,符合单调栈性质,直接入栈(栈中元素:3,2,1)
  13. 所有元素都已经遍历完毕,栈不为空并不影响,总的结果就是1+1+3+1=6
// 注意:由于栈中存放的是数组的下标,所以需要用到柱子的高度时,记得转换过来
public class 接雨水 {
    public int trap(int[] height) {
        // 创建一个单调栈,为了便于后面计算,栈中存放的是数组的下标
        LinkedList<Integer> stack = new LinkedList<>();
        // 第一个元素先直接入栈
        stack.push(0);
        // 保存雨水的数量
        int sum = 0;
        for (int i = 1; i < height.length; i++) {
            while (!stack.isEmpty() && height[i] >= height[stack.peek()]) {
                // 出栈的元素需要保存起来,作为底部参考值
                int temp = height[stack.pop()];
                // 判断栈是否为空,为空,接不到雨水,直接break
                if (stack.isEmpty()) {
                    break;
                }
                // 不为空,计算雨水
                int w = i - stack.peek() - 1; // 宽度
                int h = Math.min(height[i], height[stack.peek()]) - temp; // 高度
                sum += w * h;
            }
            stack.push(i);
        }
        return sum;
    }
}
在Python中,“单调栈雨水”通常是一个经典的算法问题,源于一道面试题或编程比赛题目,它涉及到了数据结构中的栈操作和数组遍历。题目背景通常是这样的:给定一个高度不均匀的柱状图,每根柱子代表一个高度h[i]。下雨开始时,如果柱子顶部低于当前水位,那么雨会从顶部溢出,然后继续下落,直到水位再次高于柱子顶部。你的任务是在每次降雨之后,计算有多少雨水能够积累到每个柱子的底部。 这个问题的关键在于维护一个单调栈,当遇到比当前栈顶元素高的柱子时,就将这个高柱子压入栈中;当遇到比栈顶元素低的柱子时,则表示当前栈顶元素代表的是之前降雨中累积的高度,因此可以弹出栈顶元素并累计这部分雨水量。最后,剩余在栈中的柱子也都能积累雨水。 Python代码实现可能会这样: ```python def trap(height): if not height: return 0 stack = [] left, right = 0, len(height) - 1 water = 0 while left < right: # 当左指针处柱子高度小于等于栈顶柱子,把左指针处的雨水累积进结果 while stack and height[left] <= height[stack[-1]]: water += (height[stack.pop()] - height[left]) # 把左指针对应的柱子压入栈 stack.append(left) left += 1 # 当右指针处柱子高度大于等于栈顶柱子,把右指针处的雨水累积进结果 while stack and height[right] >= height[stack[-1]]: water += (height[right] - height[stack.pop()]) # 把右指针对应的柱子压入栈 stack.append(right) right -= 1 return water ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值