【单调栈】开区间和闭区间玩法

前言:

lc周赛遇到单调栈,对于一些解法还是又不太理解的地方,所以这里尝试进行一些记录和理解。

【初级理解】

单调栈根据定义就是一个栈,类似于排队的时候,后面的人想找前面第一个比他高的人,他的视线也只能找到那个,而在前面的更矮的都被遮住了,所以这些人是不可见的,意味着你可以在栈中把它删掉。

比如LC496找下一个更大的元素,这是最简单的版本,就是更小就不要,因为对你而言是可见的,对更高的那个人的后面的人而言则不可见,因为背对着,只能往前面看嘛。

class Solution:
    def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
        ans=[-1]*len(nums2)
        stack=[]
        for i,num in enumerate(nums2):
            while stack and num<nums2[stack[-1]]:
                stack.pop()
            if stack:ans[i]=stack[-1]
            stack.append(i)
        print(ans)
        return [ nums2[stack[x]] if stack[x]>=0 else -1 for x in nums1 ]

【中级理解】

对于有一些题,共性来说就是求一个子数组里面的与最大最小值相关的,比如LC2104求每个子数组中最大元素和最小元素的差值。LC1856子数组最小乘积的最大值,以此题为例。考虑范围相关的,其实有开区间和闭区间两种写法,闭区间考虑的是如上想法,特点是,不用左右添加哨兵。从理解上看,闭区间需要两次分别从左往右看,和从右往左看,找到第一个比它小的,通常实际中也不会取这两个端点值,这里是因为前缀和其实取不到那个点,所以右边直接取端点,左边取取不到的那个,所以+1。

《闭区间写法》

class Solution:
    def maxSumMinProduct(self, nums: List[int]) -> int:
        n, ans, Mod = len(nums), 0, int(1e9) + 7
        sum_nums = list(accumulate(nums,initial=0))
        sk1, sk2, left, right = [], [], [-1] * n, [n] * n
        for i, e in enumerate(nums):
            while sk1 and e <= nums[sk1[-1]]:
                sk1.pop()
            if sk1: left[i] = sk1[-1]+1
            else: left[i] = 0
            sk1.append(i)
        for i in range(n-1, -1, -1):
            while sk2 and nums[i] <= nums[sk2[-1]]:
                sk2.pop()
            if sk2: right[i] = sk2[-1]
            else: right[i] = n
            sk2.append(i)
        for i in range(n):
            s = sum_nums[right[i]]-sum_nums[left[i]]
            ans = max(ans, nums[i]*s)
        return ans % Mod

《开区间写法》

class Solution:
    def maxSumMinProduct(self, nums: List[int]) -> int:
        st=[]
        # 前缀和
        pre=list(accumulate(nums, initial = 0))
        # 开区间,取最小,因为所求为最小值,两边取不到
        nums=[-inf]+nums+[-inf]
        ans=-inf
        for i,v in enumerate(nums):
            while st and nums[st[-1]]>v:
                ind=st.pop()
                s=st[-1] if st[-1] else 0
                # 弹出的最小值区间范围为(ind,i),两边取不到
                # 前缀和正好也是取不到,主要注意的是这里的i比pre的i大1
                ans=max(ans,nums[ind]*(pre[i-1]-pre[s]))
            st.append(i)
        return ans%(10**9+7)

开区间只用一次遍历,因为要加哨兵所以我一般理解成开区间方式。它是取弹出的元素,对于它而言栈中上面那个肯定比它小,因为前面更大的在遍历中一定弹出去了,同时目前遍历的元素也比它小,我最开始理解的时候,以为它是取最大,因为这两个端点都比它小,但实际上,反倒是抛开两个端点后,你会发现它其实是该开区间范围内最小的元素,不然其他元素肯定都成为端点了对吧,由此直接得到包含最小值的开区间范围,进而可以直接求值。

【高级理解】

暂时不会,还只是初学。待填坑。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值