单调栈的应用

江湖流传着一种所谓的“单调栈”的应用
其实这是栈的一个特殊用法,通常实践中不需要真的构建一个单调栈数据结构。我们只需要对一个顺序容器稍微控制一下它的进出次序,就能达到想要的效果。

单调栈的意图是在栈顶的元素始终保持着最大或者最小。

以递增栈为例。我们把一组无序的序列一次加入单调栈

8, 6, 5, 10, 2, 7, 11, 4

构建一个空栈

  • 第一步,将8 加入栈中;——这一步没有什么特别的
  • 第二步,将6 加入栈中; —— 6 < 8 (栈顶)元素,这样从顶到底依次递增,,没毛病。这一步还是没什么特别
  • 第三步,将5 加入栈中
  • 第四步, 将10入栈,由于10 > 5 因此,第三步入栈的 5 需要弹出,直到弹完,最后加入 10;这时候栈内只有一个元素10
  • 第五步,接着入2,由于 2 < 10 , 没毛病。 栈内元素有2个: 2 ,10
  • 第六步,加入7. 因为 7 > 2 , 弹出 2 ,栈内元素继续保持2个:7, 10
  • 第七步,加入11 ,因为11 > 7, 依次弹出7, 10 ,加入11
  • 第八步,加入 4,因为4 < 11 ; 4加入栈,栈内元素 11,4

整个过程保持栈内总顶部到底部一次递增的关系,并且每个元素必须要入栈。

这种奇怪的次序有什么作用?

可以看出每次需要弹出元素的时候,遇到一次数是数组中每个元素右侧第一个比它大的元素。

有点拗口

比方说第一余姚需要弹出的时候,是数字10,这表明前面的 8, 6, 5 的右侧第一个较大元都是10,我们用一个新的映射数组 N 表示

N[8] = 10
N[6] = 10
N[5] = 10

第二次弹出2 的时候,遇到的元素是 7,所以这里
N[2] = 7

第三次遇到11 的时候,接连弹出 7, 10
因此
N[10] = 11
N[7] = 11

11, 4 没有出现弹出,所以记作
N[11] = -1
N[4] = -1

我们来编写一下这个程序——计算数组下一个更大元是什么,没有就是 -1

def next_greater(array: list) -> dict:
    d = defaultdict(default=-1)
    st = []  # a list but can be used as a stack
    for item in array:
        d[item] = -1
        while len(st) > 0 and st[-1] < item:
            i = st.pop()
            d[i] = item
        st.append(item)
    return d

这里的栈实际上维护的就是一个单调递增栈——每个新的元素进来之后保证栈顶到栈底单调递增。

我们来它的另一个应用——接雨水

接雨水是一个最优问题——使用动态规划可以用记忆搜索的方法算出每根柱子左边最大,右边最大,然后计算出最多的接水量。

实际上还可以用单调栈的方式计算每根柱子左侧最近一根最高,右侧最近的一根最高柱,此时意味着当前的柱子可以蓄水。

def trap(self, height: List[int]) -> int:
        st = []
        water = 0
        for i, h in enumerate(height):
            while len(st) > 0 and height[st[-1]] < h:
                h_top_index = st.pop()
                if len(st) == 0:
                    break
                h_left_index = st[-1]
                width = i - h_left_index - 1
                water += (min(height[h_left_index], h) - height[h_top_index]) * width 
# 每次遇到一个高于栈顶元素的新元素,就可以结算一次

            st.append(i)
        return water

单调栈为什么这么好用?

首先它的令人迷惑的进出顺序控制一点都不令人舒服,之所以在诸多应用中被采用——是因为它有很好的性能。

一个序列用单调栈存储,一般可以取得O(n) 的时间性能尺度。

直观的看,并不容易看出来这点,但是如果我们从另一个角度分析,每个元素永远都是只进一次,只出一次,总操作数就是2你次,而栈的入栈出栈操作室 O(1) 的,所以摊还下来,单调栈的平均操作时间都是 O(1)

处理一个序列只跟这个序列的长度 你有关。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • """1.个性化消息: 将用户的姓名存到一个变量中,并向该用户显示一条消息。显示的消息应非常简单,如“Hello ...
    她即我命阅读 10,745评论 0 6
  • 1、expected an indented block 冒号后面是要写上一定的内容的(新手容易遗忘这一点); 缩...
    庵下桃花仙阅读 3,097评论 1 2
  • 一、工具箱(多种工具共用一个快捷键的可同时按【Shift】加此快捷键选取)矩形、椭圆选框工具 【M】移动工具 【V...
    墨雅丫阅读 3,707评论 0 0
  • 跟随樊老师和伙伴们一起学习心理知识提升自已,已经有三个月有余了,这一段时间因为天气的原因休课,顺便整理一下之前学习...
    学习思考行动阅读 3,196评论 0 2
  • 一脸愤怒的她躺在了床上,好几次甩开了他抱过来的双手,到最后还坚决的翻了个身,只留给他一个冷漠的背影。 多次尝试抱她...
    海边的蓝兔子阅读 2,317评论 1 4

友情链接更多精彩内容