数据结构_单调栈及其应用

单调栈

单调栈指的是 栈内元素 单调递增 或者 单调递减的栈。
一般我们可以用stack来模拟栈。
要维持栈的单调性,需要对新来的元素进行判定。

例如:
我们需要一个单调递增栈[]。
如果stack为空直接填入即可。
新元素时如果比栈顶元素 stack[-1] 大 则 直接append()添加即可;
如果新来元素比栈顶元素stack[-1] 小 则 pop()循环直到大即可。
(对于相等值的元素,可以保留,也可以直接pop,根据情况即可)

应用在解题上:

面试题59 - II. 队列的最大值

请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。

若队列为空,pop_front 和 max_value 需要返回 -1

示例 1:

输入:
[“MaxQueue”,“push_back”,“push_back”,“max_value”,“pop_front”,“max_value”]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]
示例 2:

输入:
[“MaxQueue”,“pop_front”,“max_value”]
[[],[],[]]
输出: [null,-1,-1]

解答:
需要一个工具用来记录最大值,并且满足最大值pop掉时可以得到第二大的值。
1 排除记录数字,单个数字记录在删除时无法得到第二大的值
2 想到单调栈:
1)队列添加元素,先进先出,保留最大值,运行一个单调递减栈。
2) 队列用stack表示,增加用append() ,删除用pop(0);单调栈保持一致性,则增加时判断如果新来元素比较小,直接添加到最后面,如果比较大,pop(-1)直到比较小后直接添加。
3)最大值时直接判定单调栈的头 stack[0]即可
4)队列pop时,用que[0] 与 stack[0]做比较。(记住对应记住当前的最大值)。如果等于则 同时pop(0)即可。小于则前者pop后者不做处理。

代码:

class MaxQueue:

    def __init__(self):
        self.que = []
        self.stack = []


    def max_value(self) -> int:
        if not self.que:
            return -1
        
        return self.stack[0]


    def push_back(self, value: int) -> None:
        self.que.append(value)
        
        while self.stack and self.stack[-1] < value:
            self.stack.pop()
        self.stack.append(value)


    def pop_front(self) -> int:
        if not self.que:
            return -1
        else:  
            x = self.que.pop(0)
            if x == self.stack[0]:
                self.stack.pop(0)
            return x

84. Largest Rectangle in Histogram

Given n non-negative integers representing the histogram’s bar height where the width of each bar is 1, find the area of largest rectangle in the histogram.

Above is a histogram where width of each bar is 1, given height = [2,1,5,6,2,3].

The largest rectangle is shown in the shaded area, which has area = 10 unit.

Example:

Input: [2,1,5,6,2,3]
Output: 10

解答:
1 最开始基本职能想到暴力解法,就是n^2的遍历,一次次比较最后得到最大值。
2 想想 排除一些不必要的步骤(比如说某一段比较是之前已经比较了的)
3 中心扩展法(及其优化):
1) 一个矩形,如果遇到周边递减的情况,就是其当前的底部边界,高度为其自身边界。
2)左右两个边界,可以按照数组的属性,得到每一个高度 对应的左右边界。然后得到最大值。
3) 优化,遍历时,存储边界的值,已经找到的可以依托其直接跳到其边界(比如index6 的左边界到index5了 然后直接跳往index5的左边界做比较,因为index5前一步已经找到边界了,index6能到5证明 .value 是 >= 6.value的)

中心扩展优化代码:

class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        n = len(heights)
        l,r = [-1]*len(heights),[n]*len(heights)
        for i in range(1,n):
            j=i-1
            while j>=0 and heights[j]>=heights[i]:
                j=l[j]
            l[i] = j
        for i in range(n-2,-1,-1):
            j = i+1
            while j<=n-1 and heights[j]>=heights[i]:
                j=r[j]
            r[i] = j
        res = 0
        #print(l,r)
        for i in range(0,n):
            res = max(res,heights[i]*(r[i]-l[i]-1))
        return res

4 看到这种寻找元素旁边比它小的值时,就可以使用单调栈了:
1) 为了防止单调栈溢出,给原数组添加双[0]边界
2)从1到倒数第二开始遍历。维持一个单调增的栈
3)一旦出现需要pop的情况,就进行一个面积的计算(即碰到了当前高度的右边界,而左边界本身就在栈的顶上)
4)根据公式计算面积,然后比较最大面积

代码:

class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        n = len(heights)
        l,r = [-1]*len(heights),[n]*len(heights)
        for i in range(1,n):
            j=i-1
            while j>=0 and heights[j]>=heights[i]:
                j=l[j]
            l[i] = j
        for i in range(n-2,-1,-1):
            j = i+1
            while j<=n-1 and heights[j]>=heights[i]:
                j=r[j]
            r[i] = j
        res = 0
        #print(l,r)
        for i in range(0,n):
            res = max(res,heights[i]*(r[i]-l[i]-1))
        return res

739. 每日温度

请根据每日 气温 列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。

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

提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。

解答:
1 暴力解答:n^2的双层遍历,超时
2 一般来说,遇到类似的寻找某一侧的更高值(类似84寻找左右双边第一次更高的值),就可以采用单调栈的写法。
考虑 递增栈还是递减栈。递增栈的话,遇到大于stack[-1]的值就继续插入,否则pop直到大于,对于第一个值第二大的情况,无法找到其右边界;所以采用递减栈,新的val < stack[-1]时,直接插入最后,否则pop直到满足,pop出来的val 其第一个右边界就是当前等待插入值,记录索引即可。
这个方法更新的右边界index不是顺序更新的,所以采用元组tuple()保存index,val 然后在取出时将对应index的val改为右边界的index即可。
code:

class Solution:
    def dailyTemperatures(self, T):
        l = len(T)

        stack = []
        temp = [i for i in range(l)]
        #count = 0
        for i in range(l):
            if not stack or stack[-1][1] >= T[i]:
                stack.append((i,T[i]))
            else:
                while stack and stack[-1][1] < T[i]:
                    t = stack.pop()
                    temp[t[0]] = i
                stack.append((i,T[i]))


        #print(temp)
        for i in range(l):
            temp[i]-=i

        return temp
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值