单调栈应用总结

栈基本概念:https://blog.csdn.net/qq_19446965/article/details/102982047

 

单调栈

  • 单调递减栈:数据出栈的序列为单调递减序列
  • 单调递增栈:数据出栈的序列为单调递增序列

单调栈模板:

for (遍历这个数组)
    if (栈空 || 栈顶元素>=或者<=当前比较元素):
          入栈
    else:
        while (栈不为空 && 栈顶元素<或者>当前元素)
              栈顶元素出栈;
              更新结果;
        当前数据入栈;

1、单调递减栈

题目:每日温度

根据每日 气温 列表,请重新生成一个列表,对应位置的输入是你需要再等待多久温度才会升高超过该日的天数。如果之后都不会升高,请在该位置用 0 来代替。

例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/daily-temperatures/

    def dailyTemperatures(self, T: List[int]) -> List[int]:
        res_list = [0]*len(T)
        stack = []
        for i in range(len(T)):
            while stack and T[i] > T[stack[-1]]:
                res_list[stack[-1]] = i - stack[-1]
                stack.pop()
            stack.append(i)
        return res_list

2、单调递增栈

题目:柱状图中最大的矩形

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

求在该柱状图中,能够勾勒出来的矩形的最大面积。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/largest-rectangle-in-histogram/

如上图所示,从左到右处理直方,当 i=4 时,小于当前栈顶(即直方3),对于直方3,无论后面还是前面
的直方,都不可能得到比目前栈顶元素更高的高度了,处理掉直方3(计算从直方3到直方4之间的矩形的面
积,然后从栈里弹出);对于直方2也是如此;直到碰到比直方4更矮的直方1。

这就意味着,可以维护一个递增的栈,每次比较栈顶与当前元素。如果当前元素大于栈顶元素,则入栈,
否则合并现有栈,直至栈顶元素小于当前元素。结尾时入栈元素0,重复合并一次。 

 def largestRectangleArea(self, heights):
        heights = [0] + heights + [0]  # 添加左右两侧的特殊边界使得只有一个柱子时,也可以形成一个倒V型
        lens = len(heights)
        max_high = heights[0]
        stack = []
        for i in range(lens): 
            while stack and heights[i] < heights[stack[-1]]:
                peek =  stack.pop()          
                max_high = max((i - stack[-1] - 1) * heights[peek], max_high)  # 对于stack[-1]来说其right_i=i,left_i=stack[-2]
            stack.append(i)

        return max_high

本题其余扩展参考另一篇文章:https://mp.csdn.net/console/editor/html/82048028

3、单调栈+前缀和

题目1:和至少为 K 的最短子数组

返回 A 的最短的非空连续子数组的长度,该子数组的和至少为 K 。

如果没有和至少为 K 的非空子数组,返回 -1 。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/shortest-subarray-with-sum-at-least-k/

前缀和参考:https://blog.csdn.net/qq_19446965/article/details/104723466

其实这道题的方法是优先队列,和单调栈区别就是pop元素的位置是从list首。

关于单调队列:

  • 单调队列的队首元素一定是最值元素;
  • 单调队列队尾进元素时和单调栈一样,对于破坏单调性的元素直接弹出队尾,
  • 这样就能保证对于单调队列中的每个元素,其前面的相邻元素一定是原数组向左第一个大于或小于当前元素的值;
  • 队列中最后都有那些值取决于队尾进的是谁;

因为求最大值时候的遍历方向和pop方向:

def shortestSubarray(self, A, K):
        N = len(A)
        B = [0] * (N + 1)
        for i in range(N): B[i + 1] = B[i] + A[i]  # 求前缀和
        d = collections.deque() # 优先队列
        res = N + 1
        for i in range(N + 1):
            while d and B[i] - B[d[0]] >= K: 
                res = min(res, i - d.popleft())
            while d and B[i] <= B[d[-1]]: 
                d.pop()
            d.append(i)
        return res if res <= N else -1

 

题目2:表现良好的最长时间段

给你一份工作时间表 hours,上面记录着某一位员工每天的工作小时数。

我们认为当员工一天中的工作小时数大于 8 小时的时候,那么这一天就是「劳累的一天」。

所谓「表现良好的时间段」,意味在这段时间内,「劳累的天数」是严格 大于「不劳累的天数」。

请你返回「表现良好时间段」的最大长度。

示例 1:

输入:hours = [9,9,6,0,6,6,9]
输出:3
解释:最长的表现良好时间段是 [9,9,6]。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-well-performing-interval/

因为求最大值时候的遍历方向和pop方向:

def longestWPI(self, hours):
        # 转换数组为1或-1
        new_hours =[]
        for h in hours:
            if h > 8:
                new_hours.append(1)
            else:
                new_hours.append(-1)
        # 求前缀和
        sums = [0]
        for i in range(len(hours)):
            sums.append(sums[-1] + new_hours[i])

        # 建立单调递增栈
        stack = []
        n = len(sums)
        for i in range(n):
            if not stack or sums[stack[-1]] > sums[i]:
                stack.append(i)
        # 遍历求最大值
        res = 0
        for i in range(n-1, -1, -1):
            while stack and sums[i] > sums[stack[-1]]:
                res = max(res, i - stack[-1])
                stack.pop()
        return res

总结:

通过题目1和题目2可以看出:

  • 梯度递增方向要和遍历方向相同
  • 求最大值时候,单调递减,要从n-0,同时遍历递减序列
  • 求最小值时候,单调递增,要从0-n,同时遍历递增序列

其余栈的应用:

栈基本概念:https://blog.csdn.net/qq_19446965/article/details/102982047

接雨水:https://blog.csdn.net/qq_19446965/article/details/104144187

矩阵中最大矩形:https://blog.csdn.net/qq_19446965/article/details/82048028

基本计算器类:https://blog.csdn.net/qq_19446965/article/details/104717537

单调栈的应用:https://blog.csdn.net/qq_19446965/article/details/104720836

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python中,单调栈和单调队列是两种不同的数据结构。单调栈是一个栈,它的特点是栈内的元素是单调的,可以是递增或递减的。在构建单调栈时,元素的插入和弹出都是在栈的一端进行的。与此类似,单调队列也是一个队列,它的特点是队列内的元素是单调的,可以是递增或递减的。在构建单调队列时,元素的插入是在队列的一端进行的,而弹出则是选择队列头进行的。 单调队列在解决某些问题时,能够提升效率。例如,滑动窗口最大值问题可以通过使用单调队列来解决。单调队列的结构可以通过以下代码来实现: ```python class MQueue: def __init__(self): self.queue = [] def push(self, value): while self.queue and self.queue[-1 < value: self.queue.pop(-1) self.queue.append(value) def pop(self): if self.queue: return self.queue.pop(0) ``` 上述代码定义了一个名为MQueue的类,它包含一个列表作为队列的存储结构。该类有两个方法,push和pop。push方法用于向队列中插入元素,它会删除队列尾部小于插入元素的所有元素,并将插入元素添加到队列尾部。pop方法用于弹出队列的头部元素。 总结来说,单调栈和单调队列都是为了解决特定问题而设计的数据结构。单调栈在构建时元素的插入和弹出都是在栈的一端进行的,而单调队列则是在队列的一端进行的。在Python中,可以通过自定义类来实现单调队列的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值