单调栈
什么是单调栈
一个从栈顶到栈底元素大小有序的栈
单调栈的优势
效率高:每个元素只进栈一次,出栈一次,所以时间复杂度是\(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