leetcode(力扣)单调栈题目解析记录(python版本)

目录

单调栈讲解:

单调栈模板:

递减栈

1、当前项向右寻找第一个大于自己的元素——从右向左维护递减栈

2、当前项向右寻找第一个大于自己的元素——从左向右维护递减栈

4、当前项向左寻找第一个大于自己的元素——从右向左维护递减栈

递增栈

1、当前项向左寻找第一个小于自己的元素——从右向左维护一个递增栈

2、当前项向右寻找第一个小于自己的位置——从左到右维护一个递增栈

3、当前项向右寻找第一个小于自己的元素——从右向左维护一个递增栈

力扣题目:

496. 下一个更大元素 I

题目

答案:

解析:

503. 下一个更大元素 II

题目:

答案:

解析:

1475. 商品折扣后的最终价格

题目:

答案:

解析:

42. 接雨水 

题目:

答案:

解析: 

心得:

 901. 股票价格跨度

题目:

答案:

剑指 Offer 33. 二叉搜索树的后序遍历序列

题目:

答案:

 解析:

单调栈讲解:

单调栈
单调递增栈和单调递减栈
时间复杂度是O(n)
单调栈原理:
单调递增栈:从 栈底 到 栈顶 递增,栈顶大
单调递减栈:从 栈底 到 栈顶 递减,栈顶小

单调栈模板:

Python数据结构-单调栈(Monotone Stack) - 简书 (jianshu.com)

递减栈


1、当前项向右寻找第一个大于自己的元素——从右向左维护递减栈


def rightToleft_DownStack(nums):
    length = len(nums)
    res = [-1]*length
    stack = []
    #muns=[2,9,3,5,6,4,7]
    for i in range(length-1,-1,-1):  #从右向左  #找右边大
        while stack and stack[-1] <= nums[i]:  #不符合递减栈,出栈删除  从大到小
            stack.pop()
        if stack :
            print(stack[-1])
            res[i] = stack[-1]
        stack.append(nums[i])   #大的先入栈(栈底)
    return res
nums=[2,9,3,5,6,4,7]
res=rightToleft_DownStack(nums)
print(res)

2、当前项向右寻找第一个大于自己的元素——从左向右维护递减栈


def leftToright_DownStack(nums):
    length = len(nums)
    res,stack = [-1]*length,[]
    for i in range(length):  #从左向右  
      while stack and nums[stack[-1]]< nums[i]:  #找右边大
          idx = stack.pop()
          res[idx] = nums[i]
      stack.append(i)
    return res
nums=[2,9,3,5,6,4,7]
res=leftToright_DownStack(nums)
print(res)
           
3、当前项向左寻找第一个大于自己的元素——从左向右维护递减栈


def leftToright_DownStack2(nums):
    length = len(nums)
    res,stack = [-1]*length,[]
    for i in range(length):  #从左向右 
      while stack and nums[i] >= stack[-1]:  #递减都是当前大于栈顶,找左边大
            stack.pop()
      if stack:
            res[i]=stack[-1]
      stack.append(nums[i])
    return res
nums=[2,9,3,5,6,4,7]
res=leftToright_DownStack2(nums)
print(res)


4、当前项向左寻找第一个大于自己的元素——从右向左维护递减栈


def rightToleft_DownStack2(nums):
    length = len(nums)
    res = [-1]*length
    stack = []
    for i in range(length-1,-1,-1):  #从右向左维护
        while stack and nums[stack[-1]] <= nums[i]:  #找左边大
            idx = stack.pop()
            res[idx] = nums[i]
        stack.append(i)
    return res
nums=[2,9,3,5,6,4,7]
res=rightToleft_DownStack2(nums)
print(res)

递增栈


1、当前项向左寻找第一个小于自己的元素——从右向左维护一个递增栈


def RightToLeftUpStack2(nums):
    length = len(nums)
    res,stack = [-1]*length,[]
    for i in range(length-1,-1,-1): #从右向左
        while stack and nums[i]<=nums[stack[-1]]:  #找左边小
            idx = stack.pop()
            res[idx] = nums[i]
        stack.append(i)
    return res
nums=[2,9,3,5,6,4,7]
res=RightToLeftUpStack2(nums)
print(res)
                   


2、当前项向右寻找第一个小于自己的位置——从左到右维护一个递增栈


def leftTorightUpStack(nums):
    length = len(nums)
    res,stack = [-1]*length,[]
    for i in range(length):  #从左向右
        while stack and nums[i] <= nums[stack[-1]]:  #找右边小
            idx = stack.pop()
            res[idx] = nums[i]
        stack.append(i)
    return res
nums=[2,9,3,5,6,4,7]
res=leftTorightUpStack2(nums)
print(res)

3、当前项向右寻找第一个小于自己的元素——从右向左维护一个递增栈


def RightToLeftUpStack(nums):
    length = len(nums)
    res,stack = [-1]*length,[]
    for i in range(length-1,-1,-1): #从右向左
        while stack and nums[i]<= stack[-1]:  #找右边小
            stack.pop()
        if stack:
            res[i] = stack[-1]
        stack.append(nums[i])
    return res
nums=[2,9,3,5,6,4,7]
res=RightToLeftUpStack2(nums)
print(res)
              

力扣题目:

496. 下一个更大元素 I

nums1 中数字 x 的 下一个更大元素 是指 x 在 nums2 中对应位置 右侧 的 第一个 比 x 大的元素。

给你两个 没有重复元素 的数组 nums1 和 nums2 ,下标从 0 开始计数,其中nums1 是 nums2 的子集。

对于每个 0 <= i < nums1.length ,找出满足 nums1[i] == nums2[j] 的下标 j ,并且在 nums2 确定 nums2[j] 的 下一个更大元素 。如果不存在下一个更大元素,那么本次查询的答案是 -1 。

返回一个长度为 nums1.length 的数组 ans 作为答案,满足 ans[i] 是如上所述的 下一个更大元素 。

题目

示例 1:

输入:nums1 = [4,1,2], nums2 = [1,3,4,2].
输出:[-1,3,-1]
解释:nums1 中每个值的下一个更大元素如下所述:
- 4 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。
- 1 ,用加粗斜体标识,nums2 = [1,3,4,2]。下一个更大元素是 3 。
- 2 ,用加粗斜体标识,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。

示例 2:

输入:nums1 = [2,4], nums2 = [1,2,3,4].
输出:[3,-1]
解释:nums1 中每个值的下一个更大元素如下所述:
- 2 ,用加粗斜体标识,nums2 = [1,2,3,4]。下一个更大元素是 3 。
- 4 ,用加粗斜体标识,nums2 = [1,2,3,4]。不存在下一个更大元素,所以答案是 -1 

答案:

        提供两种接替方案,第一种是根据下表进行单调栈、第二种是根据元素值。其中方法二为官方答案。

def nextGreaterElement(nums1, nums2):
        """
        :type nums1: List[int]
        :type nums2: List[int]
        :rtype: List[int]
        """
        len1,len2=len(nums1),len(nums2)
        stack = []
        res = {}
        # 方法一
        # for i in range(len2):
        #     while stack and nums2[i]> nums2[stack[-1]]:
        #         idx = stack.pop()
        #         res[nums2[idx]] = nums2[i]
            
        #     if (stack and nums2[i] < nums2[stack[-1]]):
        #         print(nums2[stack[-1]])
        #         res[nums2[stack[-1]]] = -1 
        #     stack.append(i)
        # #最后一个数字无法计算,手动添加
        # res[nums2[-1]] = -1

        # 方法二,官方答案
        for num in reversed(nums2): #从右向左维护
            while stack and num > stack[-1]:  # 寻找右面最大
                stack.pop()
            res[num] = stack[-1] if stack else -1
            stack.append(num)
            
        return [res[j] for j in nums1]
nums1=[4,1,2]
nums2=[1,3,4,2]
print(nextGreaterElement(nums1,nums2))

解析:

Q1:考虑方法一和方法二的区别:

方法一是根据索引进行求解,即正向求解(维护方向和求解方向一致),因此会导致出栈元素即为所求,需要保存结果。

方法二是根据数值进行求解,及反向求解(维护方向和求解方向相反),因此出栈元素为需要删除的元素,不需要保存。而栈顶元素需要保存到结果中。

由于本题用到了map(索引),将nums2的数值作为索引,因此使用方法二个人觉得更加简洁。并且方法二可以对无解元素直接赋值-1,而方法一无解元素不会进入while循环。当然也可以考虑在对map进行初始化时赋值-1。

503. 下一个更大元素 II

题目:

给定一个循环数组 nums ( nums[nums.length - 1] 的下一个元素是 nums[0] ),返回 nums 中每个元素的 下一个更大元素 。

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

示例 1:

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

示例 2:

输入: nums = [1,2,3,4,3]
输出: [2,3,4,-1,4]

提示:

  • 1 <= nums.length <= 104
  • -109 <= nums[i] <= 109

答案:

class Solution(object):
    def nextGreaterElements(self, nums):
        """
        :type nums: List[int]
        :rtype: List[int]
        """
        length = len(nums)
        res,stack = [-1]*length,[]
        for i in range(2*(length-1),-1,-1)
            while stack and stack[-1] <= nums[i%length]:  
        #判断时相当于i%length取值范围[0-2],
        #     # 从下标2开始判断和后面元素
        #     #从而可知类似判断需要先对后面的栈赋值,或者正常判断只输出0-2下标部分。
                 stack.pop()
            if stack:
                 res[i%length] = stack[-1]
            stack.append(nums[i%length])

        return res

解析:

 官方答案,隐形拉直nums=[1,2,1]===>[1,2,1,1,2]

  •         初始化取n = length
  •         实际上实现的是先[length/2-1](也就是用i%(length)得到的)位置入栈,然后判断[0]位置。
  •         然后判断[2]和[0] 利用取模操作
  •         抽象出来也就是,先[n/2-1]坐标元素入栈,然后判断[n/2-2]坐标元素
  •         以此类推判断[n/2-i]和[n/2-i+1]的元素直至[n/2-1]为[0]为止。
  •         开始第二阶段循环,判断[0]和[n]
  •         直至[n]为length/2位置

1475. 商品折扣后的最终价格

题目:

给你一个数组 prices ,其中 prices[i] 是商店里第 i 件商品的价格。

商店里正在进行促销活动,如果你要买第 i 件商品,那么你可以得到与 prices[j] 相等的折扣,其中 j 是满足 j > i 且 prices[j] <= prices[i] 的 最小下标 ,如果没有满足条件的 j ,你将没有任何折扣。

请你返回一个数组,数组中第 i 个元素是折扣后你购买商品 i 最终需要支付的价格。

示例 1:

输入:prices = [8,4,6,2,3]
输出:[4,2,4,2,3]
解释:
商品 0 的价格为 price[0]=8 ,你将得到 prices[1]=4 的折扣,所以最终价格为 8 - 4 = 4 。
商品 1 的价格为 price[1]=4 ,你将得到 prices[3]=2 的折扣,所以最终价格为 4 - 2 = 2 。
商品 2 的价格为 price[2]=6 ,你将得到 prices[3]=2 的折扣,所以最终价格为 6 - 2 = 4 。
商品 3 和 4 都没有折扣。

示例 2:

输入:prices = [1,2,3,4,5]
输出:[1,2,3,4,5]
解释:在这个例子中,所有商品都没有折扣。

示例 3:

输入:prices = [10,1,1,6]
输出:[9,0,1,6]

答案:

class Solution(object):
    def finalPrices(self, prices):
        """
        :type prices: List[int]
        :rtype: List[int]
        """
        
        length = len(prices)
        res,stack = [0]*length,[]
# Python中把一个list赋给新的list,改变其中一个list,两个list都会改变
#从右向左维护
        for i  in range(length-1,-1,-1):
            while stack and stack[-1] > prices[i]:
                stack.pop()
            res[i] = prices[i]-stack[-1] if stack else  prices[i]
                
            stack.append(prices[i])
        return res

解析:

翻译:

  • j>i向右寻找   prices[j]<=prices[i]的最小下标 --》 第一个最小值
  • 单调栈(递增)
  • 返回数组第 i 个元素是折扣后你购买商品 i 最终需要支付的价格   prices[i] = prices[i]-prices[j]

42. 接雨水 

题目:

给定 n 个非负整数表示每个宽度为 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

提示:

  • n == height.length
  • 1 <= n <= 2 * 104
  • 0 <= height[i] <= 105

答案:

class Solution(object):
    def trap(self, height):
        """
        :type height: List[int]
        :rtype: int
        """
        ans = 0
        length = len(height)
        stack = []
        if length <3:
            return 0;
        
        #向右找到第一个大,递减
        #需要长度,下标
        #从左向右遍历
        for i in range(length):
            while stack and height[stack[-1]] < height[i]:
                idx = stack.pop()
                if stack:
                    ans += (min(height[i], height[stack[-1]]) -  height[idx])*(i - stack[-1] - 1)
            stack.append(i)
        return ans

解析: 

首先,如果只有两个柱子就无法接雨水。

其次,根据单调栈的特点,不难发现height[idx]小于height[stack[-1]]和height[i],通俗来讲就是出现一个坑,可以接雨水。注意:如果栈为空就意味着水会溜走,不用计算。

最后,把每个坑里的水加在一起

心得:

 901. 股票价格跨度

题目:

设计一个算法收集某些股票的每日报价,并返回该股票当日价格的 跨度 。

当日股票价格的 跨度 被定义为股票价格小于或等于今天价格的最大连续日数(从今天开始往回数,包括今天)。

  • 例如,如果未来 7 天股票的价格是 [100,80,60,70,60,75,85],那么股票跨度将是 [1,1,1,2,1,4,6] 。

实现 StockSpanner 类:

  • StockSpanner() 初始化类对象。
  • int next(int price) 给出今天的股价 price ,返回该股票当日价格的 跨度 。

示例:

输入:
["StockSpanner", "next", "next", "next", "next", "next", "next", "next"]
[[], [100], [80], [60], [70], [60], [75], [85]]
输出:
[null, 1, 1, 1, 2, 1, 4, 6]

解释:
StockSpanner stockSpanner = new StockSpanner();
stockSpanner.next(100); // 返回 1
stockSpanner.next(80);  // 返回 1
stockSpanner.next(60);  // 返回 1
stockSpanner.next(70);  // 返回 2
stockSpanner.next(60);  // 返回 1
stockSpanner.next(75);  // 返回 4 ,因为截至今天的最后 4 个股价 (包括今天的股价 75) 都小于或等于今天的股价。
stockSpanner.next(85);  // 返回 6

提示:

  • 1 <= price <= 105
  • 最多调用 next 方法 104 次

答案:

class StockSpanner:
    def __init__(self):
        self.stack = [(float('inf'), 0)]
        self.count = 0
    
    def next(self, price):
        """
        :type price: int
        :rtype: int
        """
        self.count += 1
        while self.stack and self.stack[-1][0] <= price:
            self.stack.pop()
        res = self.count - self.stack[-1][1]   
        self.stack.append((price, self.count))
        return res

剑指 Offer 33. 二叉搜索树的后序遍历序列

题目:

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。

参考以下这颗二叉搜索树:

     5
    / \
   2   6
  / \
 1   3

示例 1:

输入: [1,6,3,2,5]
输出: false

示例 2:

输入: [1,3,2,6,5]
输出: true

提示:

  1. 数组长度 <= 1000

答案:

class Solution(object):
    def verifyPostorder(self, postorder):
        """
        :type postorder: List[int]
        :rtype: bool
        """
        stack = []
        length = len(postorder)
        root = 99999999
        for i in range(length-1,-1,-1):
            if root < postorder[i]:  #根节点左边所有值都应该小于根节点,若大于返回错误
                return False
            while stack and stack[-1] > postorder[i]:  #递增    #根节点右边所有值都大于根节点
                root = stack.pop()  #找到根节点
            stack.append(postorder[i])
        return True

 解析:

首先要掌握二叉树以及其遍历的规律方法

之后,我们可以发现根节点左面都小于根节点的值;根节点的右面都大于根节点的值

由此,我们只需要每次找到根节点就好啦~

利用递归栈,可以是一个递增的递归栈(也就是从右向左找到第一个小于的值),此时栈顶元素一定是大于左侧元素的,出栈就为根节点了(可以理解为一个左小右大的分界)

此时,判断如果取出的根root和左边元素依次对比,若存在小于左边元素的就说明不是搜索二叉树。

其他题目未完待续。。。个人愚见,还望大家指正!!!!谢谢~

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值