百题选手突破题单(1)

372 阅读6分钟

掘金更文挑战

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情


单调栈

很多人说单调栈在面试、笔试中地应用很简单,全是一个套路。有一定道理,很多主要考察单调栈的问题,都是肉眼可见的“下一个最大值”问题,或者维护区间最值(与需要利用线段树的题目类型并不相同)。但也不尽然,很多题目中都可以用到单调栈的思想,尤其是数组题目,且题目方向和单调性有关的时候,很多题目都可以用上单调栈来用空间换时间。

关键词:遍历数组、动态区间、空间换时间、单调性、最值/极值。

题目基本特征:求某个元素附近最大/最小的元素,问题也会和左右元素形成的区间有关。

个人总结:

题目常以一个数组做输入,通常需要遍历数组进行某种与附近(这个“附近”通常与局部的单调性有关)元素相关的操作,且只要复杂度超过O(nlogn)就会超时,所以不得不在遍历过程中记录关键数据(通常是区间上的最值),此时O(1)的空间复杂度常常难以完成,具体例子见下文。

题目列表:

题目链接特点
剑指 Offer 30. 包含min函数的栈经典单调栈
739.每日温度存下标是一种很常见的做法。
901. 股票价格跨度同上
496. 下一个更大元素 I打破单调栈题目中只有一个数组的思维惯性。
503. 下一个更大元素 II需要额外解决循环数组的问题。
239. 滑动窗口最大值使用双端队列维护单调性,是最小/大栈的进阶题目
剑指 Offer 59 - II. 队列的最大值同上
581. 最短无序连续子数组利用出栈的意义解题,出栈即意味着不满足单调性
剑指 Offer 33. 二叉搜索树的后序遍历序列利用出栈操作
42. 接雨水利用出栈操作+区间计算
84. 柱状图中最大的矩形区间计算

建议顺序:739, 901 => 496、503、 239、剑指II 59 => 剑指 33 、581 => 84 => 42

一、 下一个更大元素

基础模板

 public int[] nextGreaterNum(int[] nums) {
        Deque<Integer> stack = new LinkedList<>();
        int n = nums.length;
        int[] ans = new int[n];
        for(int i=n-1; i>=0; i--){
            while(!stack.isEmpty() && stack.peek() <= nums[i]){
                stack.pop();
            }
            // 此时栈为空代表没有更大元素
            ans[i] = stack.isEmpty() ? -1 : stack.peek();
            stack.push(nums[i]);
        }
        return ans;
    }

单调栈的第一个变式,在栈中保存下标而不是元素本身,这种变化在题目中很常见,需要优先熟悉。

前面介绍的单调栈模板更加符合直觉,即栈中的元素是符合单调性的。但是题目常常用到下标来计算,所以很多情况下在辅助栈中直接存储下标,这是单调栈实践的第一个坎。很多题目还需要配合哨兵节点来解决一些特殊用例,如581. 最短无序连续子数组

二、一个栈 + 一个数组

单调栈通常只对一个数组进行操作,模板较为简单。在练习单调栈题目时,不小心就把模板背熟了,但可能对单调栈的理解还差很远。读者可以尝试做这两道题,看看自己理解是否透彻。

前两道题只是数组形式上的变化,本质上还是对一个数组的操作。

第三题使用双端队列维护单调性,其变化在于滑动窗口,是最小栈/最大栈问题的进阶版,值得一练。

三、利用出栈操作

单调性常常作为题目的题眼所在,单调栈在这类题目上有很大的作用。

因为栈中的元素是符合单调性,而被出栈的元素自然是打破了单调性的元素,这在题目背景中常常有特殊意义。

比如:

581. 最短无序连续子数组:如果序列递增被打破,说明当前元素属于乱序部分。

42. 接雨水:如果序列递减被打破,代表需要开始计算积水量了。相关题解

剑指 Offer 33. 二叉搜索树的后序遍历序列:这一题反映了二叉搜索树的后序遍历规律,即一个后续遍历序列的根节点一定在在节点右边,而在二叉搜索树中,这代表第一个比某数大数在该数右边。相关题解

区间最值问题:

单调性和最值相关不是理所应当吗?尤其是极大值和极小值。这一类问题,在入栈出栈的过程中,维护栈中的元素为附近的极值。至于附近是哪个附近不需要担心,当然是上一个极值到当前位置啦。因为遍历顺序一般都给了,不然区间最值问题就得用线段树了。

基础题:剑指 Offer 30. 包含min函数的栈,面试典中典,此题特殊在,不需要出栈操作。

进阶题目:剑指 Offer 59 - II. 队列的最大值239. 滑动窗口最大值,出栈保证栈中存储的是附近的最大值,整个区间的最大值要从队头取。

具体应用方法肯定变化多样,但是单调栈在其中都起到了两种作用:

  1. 存储值,用以检测单调性,这是O(1)的空间复杂度很难做到的。
  2. 弹出值,用以计算核心问题

理解了以上两点后,就可以挑战最后的区间问题了。

四、区间问题

这种题目将上面的1、3结合起来,问题一般是对每个元素的枚举才能得到,例如42题,求总积水量,84题求最值。

而想要枚举出的子问题,必须根据附近的区间来决定,例如42题,递减到递增才会存水,84题(根据高度枚举)要将左右不低于当前值的bar计算进来。