单调栈策略模板归纳

单调栈模板总结

基本概念不再多说了。因为个人常常在细节上出错,所以总结一下单调栈的考虑点和写法。

ordered_stack.png

如上图求解最大面积,由遍历的思想,遍历每一个位置,以此位置为高的最大宽度应该是 左边界为向左小于点的位置后右边界为向右小于此点的位置前 之间。即上图 B 作用的宽度为 (A, C) 之间(前开后开)。

A、C 分别为 B 作用范围取不到的左边界和右边界,为了方便下面的表述,下文就用 左边界 和 右边界 来表述。说法不严谨,但知道意思即可。

单调递增栈的解法流程

由上分析,所以我们需要找的是每一个点它的左右小于此值的位置。所以:

  1. 首先要明白一个前提,单调递增栈关键要存储的是 位置,通过原数组能拿到值;
  2. 单调递增栈因为递增,所以栈中每一个元素的左边界都是栈中的前一个元素;
  3. 上条中栈底位置的左边界要特殊处理
  4. 每一个栈外的元素可通过与栈顶元素进行比较,判断是否为栈顶元素的右边界(栈顶元素可以先弹出比较,也可以不弹出比较)
  5. 如果栈外元素 > 栈顶元素,此栈外元素入栈,进而维护了 单调递增栈
  6. 如果栈外元素 < 栈顶元素,即找到了栈顶元素的 右边界,即可以按左右边界计算作用面积
  7. 上步中弹出栈顶元素后,将栈外元素继续与栈顶元素比较,即跳转到 4,直到满足 5 中的条件,分析下一个栈外元素
  8. 直到所有栈外元素分析完,最后一个元素最终会入栈,最终只剩下栈中的元素还没有计算作用面积
  9. 依次弹出栈中元素,右边界为数组溢出位置的第一个下标,左边界为栈中前一个元素,计算作用面积
  10. 直到栈为空,即计算了所有位置的作用面积

由上流程,可以总结一下:

  • 问题的关键在于如何找到一个元素它的左右边界
  • 利用单调递增栈可以很容易找到某元素的左边界
  • 通过栈外元素与栈顶的比较,能找到栈顶元素的右边界
  • 注意上两条,可以得出,单调栈相关算法每次分析的元素是 栈顶元素

写代码注意问题

  • 因为每次判断的是栈顶元素,所以栈顶要不要出栈,哪种方式编程最简单?
    • 出栈后若栈非空则此时的栈顶元素为左边界
    • 出栈后与栈外元素比较,若栈外元素大,需要将两个都放入栈中
    • 出栈后若栈外元素小,则应该循环出栈,直到此栈外元素大于栈顶或栈空,入栈
  • 栈底元素的左边界问题,数组最后一个元素的右边界问题
    • 常见的逻辑会在循环完最后一个元素后,单独循环栈中的元素出栈,即两个主循环操作(其实第一个内部还有一个循环,总共三个)
    • 如果在原数组最左边放一个最小值,最右边放一个最小值将不用再考虑第二个循环(但注意可行性,注意不要取溢出位置的数组元素)
  • 判断栈顶与栈外元素的大小用大于还是大于等于(最后是 else)?
    • 这个要看情况,如果数组中没重复元素则没问题
    • 如果数组中有重复元素,但结果只求最大面积,那也没问题
    • 如果数组中有重复元素,但要求每个位置对应的作用面积,则需要额外添加存储结构(思考下左边界和右边界就明白了)

模板

剑指 Offer II 039. 直方图最大矩形面积 为例,讲解规划此算法的策略。让其只用一个主循环逻辑(嵌套一个算两个了)。

主要策略:

  • 栈底放一个 虚拟的边界 来解决所有节点的左边界的问题(虚拟边界 的取值靠封装取值函数保证取值安全,取值返回一个取不到的小值)
  • 数组最外层也放一个 虚拟的边界 以保证栈中元素可以都弹出来
  • 封装数组取值,覆盖虚拟边界取到比列表中最小值还小的值

第二条是去除单独的循环考虑弹出栈内元素的关键。

下方,虚拟节点 heights[-1]height[len(height)-1] 都设置为 -1,这样在代码 ① 位置注意不能取到 =,否则可能不存在 peek,如果将 heights[-1] 设为 -2 的话,① 处的条件可以取到 =


func largestRectangleArea(heights []int) int {
    stack := make([]int, 0, len(heights)) // 初始化栈

    // 初始放入 虚拟边界 和 第一个下标(虚拟边界肯定小于任何下标的值)
    stack = append(stack, -1, 0)

    // 用安全的取值函数封装取数组的取值
    getVal := func(i int ) int {
        if i < 0 || i >= len(heights) {
            return -1
        }
        return heights[i]
    }

    max := 0
    for i := 1; i <= len(heights); i++ {
        for { // 栈底有个占位的最小值,所以肯定有一个 pop 的值
            var pop int
            // 先 pop 出栈顶比较好,因为 peek 也可以通过栈顶元素获取
            pop, stack = stack[len(stack)-1], stack[:len(stack)-1]

            if getVal(pop) > getVal(i) { // ①
                peak := stack[len(stack) - 1] // 注意 peek 要在条件内,因为此条件外不保证栈中一定有元素
                if width := i - peak - 1; getVal(pop)*width > max {
                    max = getVal(pop) * width
                }
            } else if  getVal(pop) < getVal(i){
                stack = append(stack, pop)
                break
            } else { // equal
                break // 因为引入了占位的值,所以需要考虑 equal 的情况
                // 此种情况需要按题进行判断
            }
        }

        stack = append(stack, i) // 放入元素
    }

    return max
}

练习题

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,383评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,522评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,852评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,621评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,741评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,929评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,076评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,803评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,265评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,582评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,716评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,395评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,039评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,027评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,488评论 2 361
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,612评论 2 350