单调栈
1. 单调栈简介
- 单调栈(Monotone Stack):一种特殊的栈。在栈的先进后出规则基础上,要求从栈顶到栈底 的元素是单调递增(或者单调递减)。其中满足从栈顶到栈底(在本文中是,有的文章中顺序与此相反)的元素是单调递增的栈,叫做「单调递增栈」。满足从栈顶到栈底的元素是单调递减的栈,叫做「单调递减栈」
1.1 单调递增栈
- 只有比栈顶元素小的元素才能直接进栈,否则需要先将栈中比当前元素小的元素出栈,再将当前元素入栈。从而保证了栈中保留的都是比当前入栈元素大的值,并且从栈顶到栈底的元素值是单调递增的
- 过程:
- 假设当前进栈元素为 x,如果栈顶元素大于 x,则直接入栈
- 否则从栈顶开始遍历栈中元素,把小于 x 或者等于 x 的元素弹出栈,直到遇到一个大于 x 的元素为止,然后再把 x 压入栈中
1.2 单调递减栈
- 与单调递增栈相反,只有比栈顶元素大的元素才能直接进栈,否则需要先将栈中比当前元素大的元素出栈,再将当前元素入栈。从而保证了栈中保留的都是比当前入栈元素小的值,并且从栈顶到栈底的元素值是单调递减的
- 过程:
- 假设当前进栈元素为 x,如果栈顶元素大于 x,则直接入栈
- 否则从栈顶开始遍历栈中元素,把大于 x 或者等于 x 的元素弹出栈,直到遇到一个小于 x 的元素为止,然后再把 x 压入栈中
2. 单调栈的适用场景
单调栈可以在时间复杂度为O(n)的情况下,求解出某个元素左边或者右边第一个比它大或者小的元素
所以,根据方向的不同以及寻找大小关系的不同,有以下四种类型及其思路
- 寻找左侧第一个比当前元素大的元素
从左到右遍历元素,构造单调递增栈(从栈顶到栈底递增):一个元素左侧第一个比它大的元素就是将其「插入单调递增栈」时的栈顶元素。如果插入时的栈为空,则说明左侧不存在比当前元素大的元素 - 寻找左侧第一个比当前元素小的元素
从左到右遍历元素,构造单调递减栈(从栈顶到栈底递减):一个元素左侧第一个比它小的元素就是将其「插入单调递减栈」时的栈顶元素。如果插入时的栈为空,则说明左侧不存在比当前元素小的元素 - 寻找右侧第一个比当前元素大的元素
- 从左到右遍历元素,构造单调递增栈(从栈顶到栈底递增):一个元素右侧第一个比它大的元素就是将其「弹出单调递增栈」时即将插入的元素。如果该元素没有被弹出栈,则说明右侧不存在比当前元素大的元素
- 从右到左遍历元素,构造单调递增栈(从栈顶到栈底递增):一个元素右侧第一个比它大的元素就是将其「插入单调递增栈」时的栈顶元素。如果插入时的栈为空,则说明右侧不存在比当前元素大的元素
- 寻找右侧第一个比当前元素大的元素
- 从左到右遍历元素,构造单调递减栈(从栈顶到栈底递减):一个元素右侧第一个比它小的元素就是将其「弹出单调递减栈」时即将插入的元素。如果该元素没有被弹出栈,则说明右侧不存在比当前元素小的元素
- 从右到左遍历元素,构造单调递减栈(从栈顶到栈底递减):一个元素右侧第一个比它小的元素就是将其「插入单调递减栈」时的栈顶元素。如果插入时的栈为空,则说明右侧不存在比当前元素小的元素
上面的可以简写为:
- 无论哪种题型,都建议从左到右遍历元素
- 查找比当前元素大的元素就用 单调递增栈,查找比当前元素小的元素就用 单调递减栈
- 从 左侧 查找就看 插入栈 时的栈顶元素,从 右侧 查找就看 弹出栈 时即将插入的元素
3. 单调栈模板
以从左到右遍历为例:
3.1 单调递增栈模板
def monotoneIncreasingStack(nums):
stack = []
for num in nums:
while stack and num >= stack[-1]:
stack.pop()
stack.append(num)
3.2 单调递减栈模板
def monotoneDecreasingStack(nums):
stack = []
for num in nums:
while stack and num <= stack[-1]:
stack.pop()
stack.append(num)
4. 单调栈相关题目
496.下一个更大元素I
- 思路:
- 直接暴力
- 单调栈
通过对nums2数组构建单调栈来率先找到每个数对应的右边第一个比它大的数,将其存储在哈希表中,之后遍历nums1,取出哈希表中对应的数值即可
- 代码实现
- 暴力
class Solution: def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]: n, m = len(nums1), len(nums2) ans = [-1] * n for i in range(n): index = nums2.index(nums1[i]) for j in range(index, m): if nums2[j] > nums1[i]: ans[i] = nums2[j] break return ans
- 代码实现:
class Solution: def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]: dic = {} stack = [] ans = [] for num in nums2: while stack and num > stack[-1]: dic[stack[-1]] = num stack.pop() stack.append(num) for num in nums1: if num in dic: ans.append(dic[num]) else: ans.append(-1) return ans
739.每日温度
- 思路:
- 暴力
- 单调栈,通过单调栈来找到每个元素右侧第一个比它大的元素的下标,并直接计算填入答案数组中
- 单调栈代码实现
class Solution:
def dailyTemperatures(self, T: List[int]) -> List[int]:
n = len(T)
ans = [0] * n
stack = []
for i in range(n):
while stack and T[i] > T[stack[-1]]:
index = stack.pop()
ans[index] = (i- index)
stack.append(i)
return ans