单调栈及其相关题目

1. 单调栈简介

  1. 单调栈(Monotone Stack):一种特殊的栈。在栈的先进后出规则基础上,要求从栈顶到栈底 的元素是单调递增(或者单调递减)。其中满足从栈顶到栈底(在本文中是,有的文章中顺序与此相反)的元素是单调递增的栈,叫做「单调递增栈」。满足从栈顶到栈底的元素是单调递减的栈,叫做「单调递减栈」

1.1 单调递增栈

  1. 只有比栈顶元素小的元素才能直接进栈,否则需要先将栈中比当前元素小的元素出栈,再将当前元素入栈。从而保证了栈中保留的都是比当前入栈元素大的值,并且从栈顶到栈底的元素值是单调递增的
  2. 过程:
    1. 假设当前进栈元素为 x,如果栈顶元素大于 x,则直接入栈
    2. 否则从栈顶开始遍历栈中元素,把小于 x 或者等于 x 的元素弹出栈,直到遇到一个大于 x 的元素为止,然后再把 x 压入栈中

1.2 单调递减栈

  1. 与单调递增栈相反,只有比栈顶元素大的元素才能直接进栈,否则需要先将栈中比当前元素大的元素出栈,再将当前元素入栈。从而保证了栈中保留的都是比当前入栈元素小的值,并且从栈顶到栈底的元素值是单调递减的
  2. 过程:
    1. 假设当前进栈元素为 x,如果栈顶元素大于 x,则直接入栈
    2. 否则从栈顶开始遍历栈中元素,把大于 x 或者等于 x 的元素弹出栈,直到遇到一个小于 x 的元素为止,然后再把 x 压入栈中

2. 单调栈的适用场景

单调栈可以在时间复杂度为O(n)的情况下,求解出某个元素左边或者右边第一个比它大或者小的元素
所以,根据方向的不同以及寻找大小关系的不同,有以下四种类型及其思路

  1. 寻找左侧第一个比当前元素大的元素
    从左到右遍历元素,构造单调递增栈(从栈顶到栈底递增):一个元素左侧第一个比它大的元素就是将其「插入单调递增栈」时的栈顶元素。如果插入时的栈为空,则说明左侧不存在比当前元素大的元素
  2. 寻找左侧第一个比当前元素小的元素
    从左到右遍历元素,构造单调递减栈(从栈顶到栈底递减):一个元素左侧第一个比它小的元素就是将其「插入单调递减栈」时的栈顶元素。如果插入时的栈为空,则说明左侧不存在比当前元素小的元素
  3. 寻找右侧第一个比当前元素大的元素
    1. 从左到右遍历元素,构造单调递增栈(从栈顶到栈底递增):一个元素右侧第一个比它大的元素就是将其「弹出单调递增栈」时即将插入的元素。如果该元素没有被弹出栈,则说明右侧不存在比当前元素大的元素
    2. 从右到左遍历元素,构造单调递增栈(从栈顶到栈底递增):一个元素右侧第一个比它大的元素就是将其「插入单调递增栈」时的栈顶元素。如果插入时的栈为空,则说明右侧不存在比当前元素大的元素
  4. 寻找右侧第一个比当前元素大的元素
    1. 从左到右遍历元素,构造单调递减栈(从栈顶到栈底递减):一个元素右侧第一个比它小的元素就是将其「弹出单调递减栈」时即将插入的元素。如果该元素没有被弹出栈,则说明右侧不存在比当前元素小的元素
    2. 从右到左遍历元素,构造单调递减栈(从栈顶到栈底递减):一个元素右侧第一个比它小的元素就是将其「插入单调递减栈」时的栈顶元素。如果插入时的栈为空,则说明右侧不存在比当前元素小的元素

上面的可以简写为:

  1. 无论哪种题型,都建议从左到右遍历元素
  2. 查找比当前元素大的元素就用 单调递增栈,查找比当前元素小的元素就用 单调递减栈
  3. 左侧 查找就看 插入栈 时的栈顶元素,从 右侧 查找就看 弹出栈 时即将插入的元素

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

  1. 思路:
    1. 直接暴力
    2. 单调栈
      通过对nums2数组构建单调栈来率先找到每个数对应的右边第一个比它大的数,将其存储在哈希表中,之后遍历nums1,取出哈希表中对应的数值即可
  2. 代码实现
    1. 暴力
    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
    
    1. 代码实现:
    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.每日温度

  1. 思路:
    1. 暴力
    2. 单调栈,通过单调栈来找到每个元素右侧第一个比它大的元素的下标,并直接计算填入答案数组中
  2. 单调栈代码实现
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
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值