单调栈II: leetcode 581、901、42、84

leetcode 581:最短无序连续子数组

题目描述:
给你一个整数数组 nums ,你需要找出一个 连续子数组 ,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。

请你找出符合题意的 最短 子数组,并输出它的长度。

示例 1:
输入:nums = [2,6,4,8,10,9,15]
输出:5
解释:你只需要对 [6, 4, 8, 10, 9] 进行升序排序,那么整个表都会变为升序排序。

示例 2:
输入:nums = [1,2,3,4]
输出:0

示例 3:
输入:nums = [1]
输出:0

提示:
1 <= nums.length <= 104
-105 <= nums[i] <= 105

思路1: 排序
本题是要找到不满足升序部分的数组的长度,所以可以对数列进行排序,排序后对比就可以知道所求的长度。

class Solution:
    def findUnsortedSubarray(self, nums: List[int]) -> int:
        nums_s = sorted(nums)
        res = []
        for i in range(len(nums)):
            if nums_s[i] != nums[i]:
                res.append(i) 
        return max(res) - min(res) + 1 if res else 0

思路2:
因为求得是不满足单调性的长度,所以想到用单调栈求出不满足单调的位置,就可以求出来长度。
这里需要注意的是,我们可以观察到数组是这样分布的【单调上升,所求部分,单调上升】所以我们需要求两部分的临界点,采用了两次单调栈的想法,一个单调递增栈求左边边界,单调递减栈求右边边界。分别是从左右两边开始遍历的;这里需要注意的是,只用一次单调栈是无法求出两个边界的,因为栈的增减是确定的,只能求出一个;

class Solution:
    def findUnsortedSubarray(self, nums: List[int]) -> int:
        key_1, key_2 = len(nums), 0
        stack = []
        for i in range(len(nums)):
            while stack and nums[i] < nums[stack[-1]]:
                peek = stack.pop()
                key_1 = min(key_1, peek)
            stack.append(i)

        stack = []
        for j in range(len(nums)-1, -1, -1):
            while stack and nums[j] > nums[stack[-1]]:
                peek = stack.pop()
                key_2 = max(key_2, peek)
            stack.append(j)
        return max(key_2-key_1+1, 0)

思路3: 双指针
主要参考的题解链接
有一点绕,但是相对好理解。
在这里插入图片描述
在这里插入图片描述

class Solution:
    def findUnsortedSubarray(self, nums: List[int]) -> int:
        n=len(nums)
        if n==1:
            return 0
        minn=nums[n-1]
        maxn=nums[0]
        right=left=0
        for j in range(n): # 从左侧更新它的最大值
            if nums[j]<maxn:
                right=j # right 是最后一个比前面所有值最大值小的位置
            maxn=max(maxn,nums[j])
        for j in range(n-1,-1,-1): #从右侧更新它的最小值
            if nums[j]>minn:
                left=j
            minn=min(minn,nums[j])
        # left和right分别是中间部分的左右边界
        if right==left:
            return 0
        return right-left+1

leetcode 901:股票价格跨度

题目描述:
编写一个 StockSpanner 类,它收集某些股票的每日报价,并返回该股票当日价格的跨度。

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

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

示例:
输入:[“StockSpanner”,“next”,“next”,“next”,“next”,“next”,“next”,“next”], [[],[100],[80],[60],[70],[60],[75],[85]]
输出:[null,1,1,1,2,1,4,6]
解释:
首先,初始化 S = StockSpanner(),然后:
S.next(100) 被调用并返回 1,
S.next(80) 被调用并返回 1,
S.next(60) 被调用并返回 1,
S.next(70) 被调用并返回 2,
S.next(60) 被调用并返回 1,
S.next(75) 被调用并返回 4,
S.next(85) 被调用并返回 6。

注意 (例如) S.next(75) 返回 4,因为截至今天的最后 4 个价格
(包括今天的价格 75) 小于或等于今天的价格。

提示:
调用 StockSpanner.next(int price) 时,将有 1 <= price <= 10^5。
每个测试用例最多可以调用 10000 次 StockSpanner.next。
在所有测试用例中,最多调用 150000 次 StockSpanner.next。
此问题的总时间限制减少了 50%。
通过次数25,882提交次数46,142

思路: 求比当天小的连续天数,想到单调栈
在本题中单调栈中存放两个值(股票价格,比这天小的连续天数)

class StockSpanner:

    def __init__(self):
        self.stack = []

    def next(self, price: int) -> int:
        k = 1
        while self.stack and self.stack[-1][0] <= price:
            k += self.stack[-1][1]
            self.stack.pop()
        self.stack.append((price, k))
        return k
        
# Your StockSpanner object will be instantiated and called as such:
# obj = StockSpanner()
# param_1 = obj.next(price)

leetcode 42: 接雨水

题目描述:
给定 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

提示:
n == height.length
1 <= n <= 2 * 104
0 <= height[i] <= 105

解本题主要参考的题解链接
思路1:
分析可以接水的条件,就是知道当前位置的左右高度最大值,然后利用这个最大值减去自己的高度就是自己这个位置可以接到的水量。

class Solution:
    def trap(self, height: List[int]) -> int:
        # 超时
        res = 0
        for i in range(1, len(height)-1):
            max_z, maxy = 0, 0
            for z in range(0, i):
                max_z = max(max_z, height[z])
            for y in range(i+1, len(height), 1):
                maxy = max(maxy, height[y])
            res += max(min(max_z, maxy) - height[i], 0)
        return res

这样做会超时,进而引出使用动态规划的方式保存最大值,避免反复的搜索两边的最大值,一次搜索保存所有位置左右两侧的最大值。
时间复杂度:O(n^2),空间复杂度O(1)。

思路2: 动态规划记录两侧最大值

    def trap2(self, height: List[int]) -> int:
        res = 0
        dp_z, dp_y = [0] * len(height), [0]*len(height)
        for i in range(1, len(height)):
            dp_z[i] = max(dp_z[i-1], height[i-1])
        for j in range(len(height)-2, 0, -1):
            dp_y[j] = max(dp_y[j+1], height[j+1])
        # print(dp_z, dp_y)
        for k in range(1, len(height)-1):
            res += max(min(dp_z[k], dp_y[k])-height[k], 0)
        return res

时间复杂度:O(n), 空间复杂度O(n)。

思路3: 单调栈,分析一处的水量需要等到下一个比他高的位置出现才可以计算,这里参考的题解链接
单调栈是一个递减栈,遇到一个比栈顶 cur大的 r ,就开始计算,高度是 r 和 cur的下一个元素 l 的较小值(因为左边也就是下一个元素是比cur大的)-cur的高度,宽度是 r-l-1,就是这段有多长。

    def trap3(self, height: List[int]) -> int:
        # 单调栈
        stack = []
        res = 0
        for i in range(0, len(height)):
            while stack and height[stack[-1]] < height[i]:
                cur = stack.pop()
                if not stack:
                    break
                l = stack[-1]
                res += ((i - l - 1) * (min(height[i], height[l]) - height[cur]))
            stack.append(i)
        return res

时间复杂度:O(n), 空间复杂度O(n)。

思路4: 双指针
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

class Solution:
    def trap(self, height: List[int]) -> int:
        left, right = 0, len(height)-1
        max_left, max_right = 0, 0
        ans = 0
        while left <= right:
            if max_left < max_right:
                ans += max(0, max_left-height[left])
                max_left = max(max_left, height[left])
                left += 1
            else:
                ans += max(0, max_right-height[right])
                max_right = max(max_right, height[right])
                right -= 1
        return ans

时间复杂度:O(n), 空间复杂度O(1)。

leetcode 84:柱状图中最大的矩形

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

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

示例 1:
在这里插入图片描述
输入:heights = [2,1,5,6,2,3]
输出:10
解释:最大的矩形为图中红色区域,面积为 10

示例 2:
在这里插入图片描述
输入: heights = [2,4]
输出: 4

提示:
1 <= heights.length <=105
0 <= heights[i] <= 104

思路: 单调栈
这题和42题接雨水很像,都是遇到下一个更高的矩形,才知道当前的高度可以得到的矩形的面积;所以想到使用单调栈;
主要参考的题解链接
42和84是单调栈的常出题目
这里需要注意的一点就是要为数组在左右两边加上一个0,因为有些情况如果不加上两个哨兵点的话输出是不对的。
比如:[1, 2, 3]这样递增的栈没有遇到弹栈的情况,就无法得到一个结果,所以在后面加一个0;再比如[2, 1, 2]在右边添加了一个0之后变成[2, 1, 2, 0],栈的变化是[2]-[1]-[1, 2]-[1]-这是1和0进行比较,0小于1,对1进行弹栈,但是1可以确定的宽度不是2,而是3,造成这种错误的原因是之前弹出了第一个2但是我们却没有记忆,所以在最开始放置一个0,这样每次我们就去cur元素左边元素+1的位置就是要计算的宽度了,具体的见代码:

class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        maxx = 0
        stack = []
        heights = [0] + heights
        heights.append(0)
        for i in range(len(heights)):
            while stack and heights[stack[-1]] > heights[i]:
                peek = stack.pop()
                left = stack[-1]
                maxx = max(maxx, (i - left - 1) * heights[peek])
            stack.append(i)
        return maxx
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值