单调栈总结

单调栈概念

一种特殊的栈。单调栈中的元素是单调递增或单调递减的,确保栈内元素的顺序保持一致。

其实单调栈就是"栈 + 维护单调性"。

用来解决 找到一个元素右边第一个比自己大/小的元素 问题。

739. 每日温度

力扣题目链接(opens new window)

请根据每日 气温 列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。

例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。

提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。

使用单调栈主要有三个判断条件。

  • 当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况
  • 当前遍历的元素T[i]等于栈顶元素T[st.top()]的情况
  • 当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况

把这三种情况分析清楚了,也就理解透彻了

python 复制代码
class Solution:
    def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
        ans=[0]*len(temperatures)
        stack=[0]

        for i in range(1,len(temperatures)):
            if temperatures[i]<=temperatures[stack[-1]]:
                stack.append(i)
            else:
                while stack and temperatures[i]>temperatures[stack[-1]]:
                    ans[stack[-1]]=i-stack[-1]
                    stack.pop()

                stack.append(i)

        return ans
    


'''
通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了。时间复杂度为O(n)。

单调栈的本质是空间换时间,因为在遍历的过程中需要用一个栈来记录右边第一个比当前元素高的元素,优点是整个数组只需要遍历一次。

用一个栈来记录我们遍历过的元素,因为我们遍历数组的时候,我们不知道之前都遍历了哪些元素,以至于遍历一个元素找不到是不是之前遍历过一个更小的,所以我们需要用一个容器(这里用单调栈)来记录我们遍历过的元素。

在使用单调栈的时候首先要明确如下几点:

单调栈里存放的元素是什么?
单调栈里只需要存放元素的下标i就可以了,如果需要使用对应的元素,直接T[i]就可以获取。

单调栈里元素是递增呢? 还是递减呢?

栈底 ........栈顶 (递减)
左   ........右
[0]     <-

74>73,记录下来,弹出73,压入74

75>74,记录下来,弹出74,压入75

71<75,压入71,此时栈[75,71]

69<71,压入69,此时栈[75,71,69]

72>69,记录下来,弹出69,此时栈[75,71],再比较 72>71记录下来,弹出71,此时栈[75]

72<75,压入72,此时栈[75,72]

76>72,记录下来,弹出72,此时栈[75],76>75,记录下来,弹出75,此时栈[],压入76,此时栈[76]

73<76,压入73,此时栈[76,73]


'''

496.下一个更大元素 I

力扣题目链接(opens new window)

给你两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。

请你找出 nums1 中每个元素在 nums2 中的下一个比其大的值。

nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1 。

示例 1:

输入: nums1 = [4,1,2], nums2 = [1,3,4,2]. 输出: [-1,3,-1] 解释: 对于 num1 中的数字 4 ,你无法在第二个数组中找到下一个更大的数字,因此输出 -1 。 对于 num1 中的数字 1 ,第二个数组中数字1右边的下一个较大数字是 3 。 对于 num1 中的数字 2 ,第二个数组中没有下一个更大的数字,因此输出 -1 。

示例 2: 输入: nums1 = [2,4], nums2 = [1,2,3,4]. 输出: [3,-1] 解释: 对于 num1 中的数字 2 ,第二个数组中的下一个较大数字是 3 。 对于 num1 中的数字 4 ,第二个数组中没有下一个更大的数字,因此输出-1 。

提示:

  • 1 <= nums1.length <= nums2.length <= 1000
  • 0 <= nums1[i], nums2[i] <= 10^4
  • nums1和nums2中所有整数 互不相同
  • nums1 中的所有整数同样出现在 nums2 中

本题是说nums1 是 nums2的子集,找nums1中的元素在nums2中下一个比当前元素大的元素。

看上去和739. 每日温度 就如出一辙了。

几乎是一样的,但是这么绕了一下,其实还上升了一点难度。

需要对单调栈使用的更熟练一些,才能顺利的把本题写出来。

从题目示例中我们可以看出最后是要求nums1的每个元素在nums2中下一个比当前元素大的元素,那么就要定义一个和nums1一样大小的数组result来存放结果。

一些同学可能看到两个数组都已经懵了,不知道要定一个一个多大的result数组来存放结果了。

这么定义这个result数组初始化应该为多少呢?

题目说如果不存在对应位置就输出 -1 ,所以result数组如果某位置没有被赋值,那么就应该是是-1,所以就初始化为-1。

在遍历nums2的过程中,我们要判断nums2[i]是否在nums1中出现过,因为最后是要根据nums1元素的下标来更新result数组。

注意题目中说是两个没有重复元素 的数组 nums1 和 nums2

没有重复元素,我们就可以用map来做映射了。根据数值快速找到下标,还可以判断nums2[i]是否在nums1中出现过。

python 复制代码
class Solution:
    def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
        ans=[-1]*len(nums1)
        dic={}
        stack=[nums2[0]]

        for i in range(len(nums1)):
            dic[nums1[i]]=i

        for i in range(1,len(nums2)):
            if nums2[i]<=stack[-1]:
                stack.append(nums2[i])

            else:
                while stack and nums2[i]>stack[-1]:
                    if stack[-1] in dic:
                        index=dic[stack[-1]]
                        ans[index]=nums2[i]
                    stack.pop()

                stack.append(nums2[i])

        return ans


'''
从题目示例中我们可以看出最后是要求nums1的每个元素在nums2中下一个比当前元素大的元素,那么就要定义一个和nums1一样大小的数组ans来存放结果。

在遍历nums2的过程中,我们要判断nums2[i]是否在nums1中出现过,因为最后是要根据nums1元素的下标来更新ans数组。

注意题目中说是两个没有重复元素 的数组 nums1 和 nums2。

没有重复元素,我们就可以用map来做映射了。根据数值快速找到下标,还可以判断nums2[i]是否在nums1中出现过。
'''

503.下一个更大元素II

力扣题目链接(opens new window)

给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。

示例 1:

  • 输入: [1,2,1]
  • 输出: [2,-1,2]
  • 解释: 第一个 1 的下一个更大的数是 2;数字 2 找不到下一个更大的数;第二个 1 的下一个最大的数需要循环搜索,结果也是 2。

提示:

  • 1 <= nums.length <= 10^4
  • -10^9 <= nums[i] <= 10^9
python 复制代码
class Solution:
    def nextGreaterElements(self, nums: List[int]) -> List[int]:
        n=len(nums)
        ans=[-1]*n
        stack=[0]

        for i in range(1,2*n):
            if nums[i%n]<=nums[stack[-1]]:
                stack.append(i%n)

            else:
                while stack and nums[i%n]>nums[stack[-1]]:
                    ans[stack[-1]]=nums[i%n]
                    stack.pop()

                stack.append(i%n)

        return ans


'''
将两个nums数组拼接在一起,使用单调栈计算出每一个元素的下一个最大值
'''

42. 接雨水

力扣题目链接(opens new window)

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

示例 1:

  • 输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
  • 输出:6
  • 解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。

示例 2:

  • 输入:height = [4,2,0,3,2,5]
  • 输出:9

按照行来计算如图:(单调栈)

按照列来计算如图:(双指针、暴力)

双指针

python 复制代码
class Solution:
    def trap(self, height: List[int]) -> int:
        n = len(height)
        max_left = [0] * n
        max_left[0]=height[0]
        max_right = [0] * n
        max_right[-1]=height[-1]
        ans = 0

        for i in range(1, n):#// 记录每个柱子左边柱子最大高度
            max_left[i] = max(height[i], max_left[i - 1])

        for j in range(n - 2, -1, -1):#// 记录每个柱子右边柱子最大高度
            max_right[j] = max(height[j], max_right[j + 1])
		#求和
        for i in range(n):
            count = min(max_left[i], max_right[i]) - height[i]
            if count > 0:
                ans += count

        return ans

单调栈

python 复制代码
class Solution:
    def trap(self, height: List[int]) -> int:
        # stack储存index,用于计算对应的柱子高度
        stack=[0]
        ans=0

        for i in range(1,len(height)):
            if height[i]<height[stack[-1]]:
                stack.append(i)
            elif height[i]==height[stack[-1]]:
                stack.pop()
                stack.append(i)
            else:
                while stack and height[i]>height[stack[-1]]:
                    mid_height=height[stack[-1]]
                    stack.pop()

                    if stack:
                        left_height=height[stack[-1]]
                        right_height=height[i]
                        h=min(left_height,right_height)-mid_height

                        w=i-stack[-1]-1

                        ans+=h*w

                stack.append(i)

        return ans

'''
从栈底到栈顶的顺序:从大到小

栈顶和栈顶的下一个元素以及要入栈的元素,三个元素来接水!

那么雨水高度是 min(凹槽左边高度, 凹槽右边高度) - 凹槽底部高度,代码为:int h = min(height[st.top()], height[i]) - height[mid];

雨水的宽度是 凹槽右边的下标 - 凹槽左边的下标 - 1(因为只求中间宽度),代码为:int w = i - st.top() - 1 ;

当前凹槽雨水的体积就是:h * w。

'''

84.柱状图中最大的矩形

力扣题目链接(opens new window)

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

求在该柱状图中,能够勾勒出来的矩形的最大面积。

  • 1 <= heights.length <=10^5
  • 0 <= heights[i] <= 10^4

本地单调栈的解法和接雨水的题目是遥相呼应的。

为什么这么说呢,42. 接雨水 是找每个柱子左右两边第一个大于该柱子高度的柱子,而本题是找每个柱子左右两边第一个小于该柱子的柱子。

双指针

python 复制代码
class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        ans=0
        n=len(heights)
        min_left=[-1]*n
        min_right=[n]*n

        for i in range(1,n):
            t=i-1
            while t>=0 and heights[t]>=heights[i]:
                t=min_left[t]

            min_left[i]=t

        for j in range(n-2,-1,-1):
            t=j+1
            while t<n and heights[t]>=heights[j]:
                t=min_right[t]

            min_right[j]=t

        for i in range(n):
            count=heights[i]*(min_right[i]-min_left[i]-1)
            ans=max(ans,count)

        return ans
        
        
'''
记录每个柱子 左边第一个小于该柱子的下标 右边第一个小于该柱子的下标
'''

单调栈

python 复制代码
class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        
        '''
        找每个柱子左右侧的第一个高度值小于该柱子的柱子
        单调栈:栈顶到栈底:从大到小(每插入一个新的小数值时,都要弹出先前的大数值)
        栈顶,栈顶的下一个元素,即将入栈的元素:这三个元素组成了最大面积的高度和宽度
        情况一:当前遍历的元素heights[i]大于栈顶元素的情况
        情况二:当前遍历的元素heights[i]等于栈顶元素的情况
        情况三:当前遍历的元素heights[i]小于栈顶元素的情况
        '''
        
        # 输入数组首尾各补上一个0(与42.接雨水不同的是,本题原首尾的两个柱子可以作为核心柱进行最大面积尝试
        heights.append(0)
        heights.insert(0,0)
        n=len(heights)
        ans=0
        stack=[0]

        for i in range(1,n):
            # 情况一
            if heights[i]>heights[stack[-1]]:
                stack.append(i)
            # 情况二
            elif heights[i]==heights[stack[-1]]:
                stack.pop()
                stack.append(i)
            # 情况三
            else:
                # 抛出所有较高的柱子
                while stack and heights[i]<heights[stack[-1]]:
                    # 栈顶就是中间的柱子,主心骨
                    mid=stack[-1]
                    stack.pop()

                    if stack:
                        right=i
                        left=stack[-1]
                        ans=max(ans,heights[mid]*(right-left-1))

                stack.append(i)

        return ans
相关推荐
老马啸西风3 小时前
Occlum 是一个内存安全的、支持多进程的 library OS,特别适用于 Intel SGX。
网络·后端·算法·阿里云·云原生·中间件·golang
若云止水5 小时前
ngx_conf_handler - root html
服务器·前端·算法
lwewan7 小时前
26考研——查找_树形查找_二叉排序树(BST)(7)
数据结构·笔记·考研·算法
独好紫罗兰8 小时前
洛谷题单1-B2002 Hello,World!-python-流程图重构
python·算法·流程图
刚入门的大一新生8 小时前
数据结构初阶-二叉树链式
数据结构·算法
雨出8 小时前
算法学习第十六天:动态规划(补充题目)
学习·算法·动态规划
flying_13148 小时前
面试常问系列(一)-神经网络参数初始化
神经网络·算法·激活函数·正态分布·参数初始化·xavier·kaiming
uhakadotcom9 小时前
当待处理的日志到了TB级别,这些工具你不得不学起来...
算法·架构·github
এ旧栎9 小时前
蓝桥与力扣刷题(蓝桥 蓝桥骑士)
java·数据结构·算法·leetcode·蓝桥杯·二分·学习和成长
uhakadotcom10 小时前
使用airflow的10个具体实用案例
算法·面试·架构