单调栈和单调队列所学的一些问题
单调栈
单调栈中的元素要求从栈底到栈顶单调递增
遍历数组,如果元素入栈后符合单调要求则顺利入栈不符合要求则弹出栈顶元素,元素出栈时得出结果
右侧结果:待入栈元素
左侧结果:出栈后的栈顶元素
单调栈主要用来求每一个当前数左右最近的比他小/大的数,复杂度O(N),单调栈分为:
单调递增栈:从栈底(封闭端)到栈顶(开口端)元素从大到小(递减)
单调递减栈:从栈底(封闭端)到栈顶(开口端)元素从小到大(递增)
用法
注意递增和递减指的是由栈顶到栈底!
单调递增栈(从栈顶到栈底由小到大,从栈底到栈顶由大到小)可以求两侧第一个比当前元素大的元素
单调递减栈(从栈顶到栈底由大到小,从栈底到栈顶由小到大)可以求两侧第一个比当前元素小的元素
为什么要选择这样的单调性(重要):
因为我们会在弹栈时形成记录,其靠近栈底的元素是其左边的第一个比他大/小的元素。如果需要是大,那么这个元素必定比他大,因此由栈顶到栈底增大是递增栈。如果需要是小,那么这个元素一定比他小,那么由栈顶到栈底是减小是递减栈。
以求左右比当前数字小的第一个数为例,其压弹栈规则如下(先假设数组中没有重复值):
我们用单调递减栈(从栈底到栈顶由小到大)
如果新来的元素比栈顶元素大(入栈能够满足栈的单调性),那么直接入栈该元素下标
如果新来的元素比栈顶元素小(直接入栈破坏单调性),那么pop栈顶元素直至新元素比栈顶元素大
什么时候生成记录:当一个元素要从栈中弹出时生成他的记录
如何获得更小值:将要弹出元素左边第一个更小值是栈中比当前元素更靠栈底的哪个元素,将要弹出元素右边第一个更小值是使得该元素需要弹出的那个元素。
如何处理相等值
这时候栈中的每一个元素都是一个list,我们把相等元素(如果在栈中相邻)的下标可以存储到这个list中。在弹出结算时,这几个相等值被视为同一个值,他们左/右第一个小的数是一致的。(所以第一个小/大指的是严格小/大于)
单调队列
单调队列主要是为了求滑动窗口最大/最小值。单调队列是双端队列(首尾两边都可以append和pop)。具体而言,我们会在单调队列的队尾pop和append,会在队首pop
滑动窗口:只能左边界L向右移动或不动、右边界R向右移动或不动,二者不能向左移动。
基本概念
单调队列的类型:
从头到尾递减:可以求滑动窗口内的最大值
从头到尾递增:可以求滑动窗口内的最小值
为什么要选择这样的单调性:
首先规定队首的元素是我们需要的最值(这一点非常重要),所以递减队列的队首是最大值,递增队列的队首是最小值。其次我们从下面对队列中元素的理解也可以看到。从队首到队尾的元素成为所需最值的优先级需要依次递减。
在单调队列中,头和尾都可以pop,但只有尾可以append。
特别注意:单调队列里存放的是index(下标)而不是元素值(其实也可以是(value, index)这种tuple),这是因为我们无法用元素值来判断元素是否过期。但是我们在谈论元素大小时,指的不是index的大小,而是index在原数组对应value的大小。
用法
以求最大值的单调队列为例,其进出队规则如下:
该单调队列要求其中元素是从头到尾递减。遍历一个数组,所有元素依次入队。
在入队时,若该元素比队尾元素小,直接从队尾入队仍能保持单调性,那么从尾部直接入队即可。
若该元素比队尾元素大,那么要将队尾元素不停pop,直到队尾元素比该元素大(满足单调性),将该元素从队尾入队。
另外注意,当元素过期(已经不在滑动窗口内),将该元素在队首出队。
什么时候生成记录:每当形成一个窗口时就收集答案。
如何获取滑动窗口的最大值:即双端队列头部的值
如何理解:
队列中的元素表示,如果此时fix住右边界R而右移左边界L(窗口缩小),那么从队首到队尾的元素表示能够成为滑动窗口最大值的优先级(即哪些元素会依次称为最大值)。优先级高的元素应当值更大、值相同的情况下下标更晚过期(这就处理了具有重复值的情况)。
我们按照这样的理解来审视上面的进出队规则:
如果我们希望从队尾入队的元素比队尾已有的元素大,说明其称为最大值的优先级更高,所以需要pop掉已有的队尾元素。如果希望入队的元素比队尾已有元素小,说明其优先级低,所以可以直接入队。
对于重复值情况的说明:当即将入队的元素和队尾此时的元素重复的时候,新来的元素其下标更晚过期,所以其优先级更高,所以队中的旧元素应当被pop掉。因此队中的元素其实是严格递减的。
如何解决滑动窗口内的最小值问题呢?其实是一样的,不过我们把最小值放在队首,队中元素依次递增。
以上是学习记录的笔记(主要学习的是左程云的课程)