基算精讲(26)单调栈

本质思想

及时去掉无用的数据,保持栈中数据有序

视频链接

算法基础精讲

那些算法究竟是怎么想出来的?灵神将带你知其然,更知其所以然!

练习

每日温度

可以说是模板题,两个角度本质是一样的,取决于st数组留的是找到NGE的还是没找到NGE的

class Solution:
    def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
        l=len(temperatures)
        res=[0]*l
        stack=[]
        for i in range(l):
            while stack and temperatures[stack[-1]]<temperatures[i]:
                res[stack[-1]]=i-stack[-1]
                stack.pop()
            stack.append(i)
        return res

接雨水

链接

接雨水

单调栈只能处理两个之间吗?这题学会变通

class Solution:
    def trap(self, height: List[int]) -> int:
        st=[]
        ans=0
        for idx,i in enumerate(height):
            while st and height[st[-1]]<i:
                h=st.pop()
                if not len(st):
                    break
                l=st[-1]
                ans+=(min(height[l],i)-height[h])*(idx-l-1)
            st.append(idx)
        return ans

商品折扣后的最终价格

简单的运用

class Solution:
    def finalPrices(self, prices: List[int]) -> List[int]:
        st=[]
        for i,p in enumerate(prices):
            while st and p<=prices[st[-1]]:
                    prices[st.pop()]-=p
            st.append(i)
        return prices       

股票价格跨度

灵神写的太妙啦,牢记及时排除不需要的数据

class StockSpanner:

    def __init__(self):
        self.st=[(-1,inf)]
        self.cur=-1


    def next(self, price: int) -> int:
        while price>=self.st[-1][1]:
            self.st.pop()
        self.cur+=1
        self.st.append((self.cur,price))
        return self.cur-self.st[-2][0]

链表中的下一个更大节点

在链表上,差不了多少

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def nextLargerNodes(self, head: Optional[ListNode]) -> List[int]:
        ans=[0]*(10**4)
        st=[]
        cur=0
        while head:
            while st and st[-1][1]<head.val:
                ans[st.pop()[0]]=head.val
            st.append([cur,head.val])
            cur+=1
            head=head.next
        return ans[:cur]

队列中可以看到的人数

真妙啊,要仔细审题
还有配图好抽象

class Solution:
    def canSeePersonsCount(self, heights: List[int]) -> List[int]:
        st=[]
        ans=[0]*len(heights)
        for i in range(len(heights)-1,-1,-1):
            while st and st[-1]<heights[i]:
                st.pop()
                ans[i]+=1
            if st: ans[i]+=1
            st.append(heights[i])
        return ans

柱状图中最大的矩形

非常好的一题,关键点是维护左右边界,贡献法思考不需要的元素,单调栈及时维护排除,哨兵优化边界

三叶姐在这里有一个很有趣的切入角度:

最终矩形的高度必然取自某个 hs[i],因此我们可以枚举最终矩形的高度来做。

所以需要枚举高度和对应的左右边界

class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        ans=0
        st=[0]
        # 维护左右边界,贡献法思考不需要的元素,单调栈维护排除
        heights=[-1]+heights+[-1]
        for i,h in enumerate(heights):
            while heights[st[-1]]>h:
                hi=heights[st.pop()]
                ldx=st[-1]
                rdx=i
                ans=max(ans,hi*(rdx-ldx-1))
            st.append(i)
        return ans

最大矩形

太妙了,对于每一行视为底的话就是柱状图中最大的矩形啊,所以我们dp预处理一下以每一行为底时的高度(如果是0便清零,贡献法思考),然后复用上一题的代码即可

class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        ans=0
        st=[0]
        # 维护左右边界,贡献法思考不需要的元素,单调栈维护排除
        heights=[-1]+heights+[-1]
        for i,h in enumerate(heights):
            while heights[st[-1]]>h:
                hi=heights[st.pop()]
                ldx=st[-1]
                rdx=i
                ans=max(ans,hi*(rdx-ldx-1))
            st.append(i)
        return ans
    def maximalRectangle(self, matrix: List[List[str]]) -> int:
        ans=0
        n=len(matrix)
        m=len(matrix[0])
        dp=[[0 for _ in range(m+1)] for _ in range(n+1)]
        for i,v in enumerate(matrix):
            for j,vv in enumerate(v):
                if vv=="1":
                    dp[i+1][j+1]=dp[i][j+1]+1
                else:
                    try:
                        dp[i+1][j+1]=0
                    except:
                        print(i,j)
            res=self.largestRectangleArea(dp[i+1])
            ans=max(ans,res)
        return ans

更多

题单

单调栈总结

  • 两种写法的本质就是数组维护的是未找到NGE的还是找到NGE的
  • 及时去掉无用的数据,保持栈中数据有序
  • 不一定只是两个之间先关的,学会变通
  • 尝尝结合贡献法思考