0
点赞
收藏
分享

微信扫一扫

单调栈与相关题目(Python)

1. 单调栈简介

单调栈(Monotone Stack):一种特殊的栈。在栈的【先进后出】规则基础上,要求【从栈顶到栈底的元素是单调递增】。其中满足从栈顶到栈底的元素是单调递增的栈,叫做【单调递增栈】。满足从栈顶到栈底的元素是单调递减的栈,叫做【单调递减栈】。

PS: 理论看着有条理,但是有可能会觉得单调栈无用,

1.1 单调递增栈

单调递增栈:只有比栈顶元素小的元素才能直接进栈,否则需要先将栈中比当前元素小的元素出栈,再将当前元素入栈。这样就保证了:栈中保留的都是比当前入栈元素大的值,并且从栈顶到栈底的元素值是单调递增的。

单调递增栈的入栈、出栈过程如下:

  • 假设当前进栈元素为 x,如果栈顶元素大于 x,则直接入栈。
  • 否则从栈顶开始遍历栈中元素,把小于 x 或者等于 x 的元素弹出栈,直到遇到一个大于 x 的元素为止,然后再把 x 压入栈中。

【 从右向左找到第一个比本数大的数】

以数组 [2, 7, 5, 4, 6, 3, 4, 2] 为例,模拟一下「单调递增栈」的进栈、出栈过程。具体过程如下:
在这里插入图片描述
最终栈中元素为 [7, 6, 4, 2]。因为从栈顶(右端)到栈底(左侧)元素的顺序为 2, 4, 6, 7,满足递增关系,所以这是一个单调递增栈。

1.2 单调递减栈

单调递减栈:只有比栈顶元素大的元素才能进入栈,否则需要先找将栈中比当前元素大的元素出战,再将当前元素入栈。这样就保证了:”栈中保留的都是比当前入栈元素小的值,并且从栈顶到栈底的元素值是单调递增的。

单调递减栈的入栈、出栈过程如下:

  • 假设当前进栈元素为 x,如果栈顶元素大于 x,则直接入栈。
  • 否则从栈顶开始遍历栈中元素,把大于 x 或者等于 x 的元素弹出栈,直到遇到一个小于 x 的元素为止,然后再把 x 压入栈中

【 从左向右找到第一个比本数大的数】

以数组 [4, 3, 2, 5, 7, 4, 6, 8] 为例,模拟一下「单调递减栈」的进栈、出栈过程。具体过程如下:
在这里插入图片描述
最终栈中元素为 [2, 4, 6, 8]。因为从栈顶(右端)到栈底(左侧)元素的顺序为 8, 6, 4, 2,满足递减关系,所以这是一个单调递减栈。

2. 单调栈适用场景

单调栈可以在时间复杂度为 O(n)的情况下,求解出某个元素左边或者右边第一个比它大或者小的元素。
所以单调栈一般用于解决一下几种问题:

  • 寻找左侧第一个比当前元素大的元素
  • 寻找左侧第一个比当前元素小的元素
  • 寻找右侧第一个比当前元素大的元素
  • 寻找右侧第一个比当前元素小的元素

下面分别说一下这几种问题的求解方法。

2.1 寻找左侧第一个比当前元素大的元素

  • 从左到右遍历元素,构造单调递增栈(从栈顶到栈底递增):一个元素左侧第一个比它大的元素就是将其「插入单调递增栈」时的栈顶元素。如果插入时的栈为空,则说明左侧不存在比当前元素大的元素。

2.2 寻找左侧第一个比当前元素小的元素

  • 从左到右遍历元素,构造单调递减栈(从栈顶到栈底递减):一个元素左侧第一个比它小的元素就是将其「插入单调递减栈」时的栈顶元素。如果插入时的栈为空,则说明左侧不存在比当前元素小的元素

2.3 寻找右侧第一个比当前元素大的元素

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

2.4 寻找右侧第一个比当前元素小的元素

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

上边的分类解法有点绕口,可以简单记为以下条规则:

无论哪种题型,都建议从左到右遍历元素。

查找 「比当前元素大的元素」 就用 单调递增栈,查找**「比当前元素小的元素」**就用 单调递减栈。
「左侧」 查找就看 「插入栈」 时的栈顶元素,从 「右侧」 查找就看 「弹出栈」 时即将插入的元素。

3. 单调栈的应用

3.1 下一个更大元素Ⅰ

496. 下一个更大元素 I
思路
使用单调栈方法处理nums2,每个元素在 nums2中对应位置的右边的第一个更大的元素值时不需要再遍历nums2。

代码

class Solution:
    def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
        res = []
        stack = []
        num_map = {}
        for num in nums2:
            # 遇到栈顶大的数就保存到哈希表里
            while stack and num > stack[-1]:
                num_map[stack[-1]] = num
                stack.pop()
            stack.append(num)

        for num in nums1:
            res.append(num_map.get(num, -1))
        return res

将nums2 存在下一个最大值存储到哈希表num_map中的过程
在这里插入图片描述

3.2 每日温度

739. 每日温度

代码

class Solution:
    def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
        length = len(temperatures)
        ans = [0] * length
        # stack存储下标
        stack = []
        for i in range(length):
            temperature = temperatures[i]
            while stack and temperature > temperatures[stack[-1]]:
                prev_index = stack.pop()
                ans[prev_index] = i - prev_index
            stack.append(i)
        return ans

参考链接:https://leetcode-cn.com/problems/daily-temperatures/solution/mei-ri-wen-du-by-leetcode-solution/

3.3 去除重复母

316. 去除重复字母
代码

class Solution:
    def removeDuplicateLetters(self, s) -> int:
        stack = []
        remain_counter = collections.Counter(s)

        for c in s:
            if c not in stack:
                while stack and c < stack[-1] and  remain_counter[stack[-1]] > 0:
                    stack.pop()
                stack.append(c)
            remain_counter[c] -= 1
        return ''.join(stack)

参考链接:https://leetcode-cn.com/problems/remove-duplicate-letters/solution/yi-zhao-chi-bian-li-kou-si-dao-ti-ma-ma-zai-ye-b-4/

参考

[1] https://algo.itcharge.cn
[2] 数据结构教程 第 2 版 - 唐发根 著
[3] 数据结构与算法 Python 语言描述 - 裘宗燕 著

举报

相关推荐

0 条评论