单调栈

什么是单调栈

一个从栈顶到栈底元素大小有序的栈

单调栈的优势

效率高:每个元素只进栈一次,出栈一次,所以时间复杂度是\(O(N)\).

什么时候用单调栈(单调栈的特点)

单调递增(减)栈举例,对于数组中的某个元素,单调栈能在平均\(O(1)\)的时间找到离自己最近的比自己小(大)的两个元素(左边一个,右边一个),我通常在数组开头和结尾放一个INT_MIN(INT_MAX),这样就不用处理边界情况。

(递增为例)如何找到离自己最近的比自己的两个元素?

对于数组元素b,如果栈顶元素t小于b,那么t就是离b最近的左边的小元素,否则弹栈,直到找到小于b的元素,b入栈。这就是说:b入栈时可以得到b左边最近的小元素。

继续对数组遍历,如果遍历到的元素大于b就压栈,直到某元素c小于b,b出栈。这就是说:b出栈时可以得到b右边最近的小元素。

典型题目 leetcode 84

对于某个柱子b,如果我想知道以a的高度为高的最大矩形怎么办?根据上面我们说的,如果用单调递增栈,我们可以快速得到离b最近的且比b的两个柱子(左边为a,右边为c)。得到之后呢?因为a是b左边第一个比b低的柱子,言外之意是,a,b之间的柱子都比b要,既然都比b高,那么就可以作为以b的高度为高的矩形的一部分,右边同理。那么最后,a和c之间的距离就是以b的高度为高的矩形的底边长度。

单调栈求最远(最长)

求最远是单调栈的一种特殊用法,且只能求一个方向的最远,且不是每个元素的最远,而是全局元素最远。

假设要求最长子数组且子数组左端点小于右端点,先考虑哪些元素不可能做这个最长区间的左端点:考虑arr[j]是否可以做左端点,如果存在i<j , arr[i] <= arr[j],那么arr[j]一定不能做左端点,因为用arr[i]做左端点一定会更长。所以可能做左端点的值是一个严格单调递减的。所以预处理出来这个单调栈,这个过程不弹栈。

下边找右端点,从右向左遍历,如果栈顶元素小于当前元素arr[i],弹栈并计算区间长度。为什么弹栈?因为是从远到近遍历,所以左端点遇到的第一个大于自己的元素一定是最远的。
例题:https://leetcode.cn/problems/longest-well-performing-interval/description/
代码:

#单调栈找最远
class Solution:
    def longestWPI(self, arr: List[int]) -> int:
        arr = [0]+list(accumulate([1 if x>8 else -1 for x in arr]))
        n, res = len(arr), 0
        st = []
        for i,x in enumerate(arr):
            if not st or x<arr[st[-1]]:
                st.append(i)
        for i in range(n-1, -1, -1):
            while st and arr[st[-1]] < arr[i]:
                res = max(res, i-st.pop())
        return res
posted @ 2020-04-19 16:23  hellozhangjz  阅读(164)  评论(0编辑  收藏  举报