单调栈概念与技巧
总结
(1)总的来说,单调栈适合一些求解过程或者求解出的结果明显的呈现出V或者^形式的问题,这种类型的问题可以考虑使用单调栈。(可以考虑是指,按照这个特点使用单调栈不一定能绝对完成任务),总之能用单调栈解决的问题一定符合v,^,但是符合v,^形式的问题,不一定能用单调栈,具体问题具体分析(但是大部分都可以),然后选用下面的2种单调栈解决。
(2)求数组中一个元素的两端最近的大于或者小于该元素的元素(即左边最近的小于/大于它的元素,右边最近的小于/大于它的元素),可以求两端,当然只求一端也可以完成。——适用于第一种实现方式的单调栈
(3)将数组中元素按阶梯状分组,使得一个组内的数都(大于/小于)(上一个组/下一个组),求分组数——适合第二种实现方式的单调栈。
1.单调栈概念
简单来说,单调栈指的是在算法过程种维护一个栈,使得栈内元素每时每刻都是递增或者递减的(这里的递增和递减不一定要求是严格递增递减的,实际实现要根据具体需求)。
2.单调栈的实现。
单调栈2种实现方式。
1.第一种实现方式
例:
遍历下面的数组元素维护一个递减栈:
arrs = [2,4,3,6,5,7,1]
stack = []
(1).栈空2入栈 stack = [2]
(2).4>2 2出栈,4入栈 stack = [4]
(3).3<4 3入栈 stack = [4,3]
(4) stack = [6]
(5) stack = [6,5]
(6) stack = [7]
(7) stack = [7,1]
当然为了大部分算法任务的完成,入栈的元素一般是数组内元素的下标或者索引。
如上例的第(5)步,如果栈中保存的是索引或者下标则 stack = [3,4] //因为6在原数组种的下标是3,5在原数组中的下标是4。
2.第二种实现方式
例子:
同样维护一个递减栈:
arrs = [4,6,3,5,7,1,2]
stack = []
(1) 栈空4入栈 stack = [4]
(2) 6>4 stack = [4] //因为6>4 栈是递减的,所以按实现1,stack = [6],但是此时用栈顶元素(4)放到6该放的位置,所以栈为[4].
(3) 3<4 3入栈 stack = [4,3]
(4) 5>3 stack = [3] //因为5>3 5>4,所以按实现1,stack = [5],但是此时用栈顶元素(3)放到5该放的位置,所以栈为[3].
(5) 7>3 stack = [3] //同上
(6) 1<3 stack = [3,1] //小于之间插入
(7) 2>1 stack = [3,1] //2>1 2<3 所以stack = [3,2],但是此时用栈顶元素(1)放到2该放的位置,所以栈为[3,1]。
3.单调栈的特征
不论是单调栈的那种实现方式,都满足一个特征(下面使用严格递减栈来举例,递增栈恰恰相反一样):
如果某一时刻栈中元素为:[A,B,C]
则:
(1) A>B>C 显然
(2) 假如使用第一种实现方式,则原数组中,在(A,B)之间的元素arrs(A->B) < A,arrs(A->B) < B;同理,原数组中在(B,C)之间的元素都小于B,C;但是,(A,B)之间的元素没有大小关系并且和C没有大小关系,可能有的大于C,可能有的小于C。
(3) 假如使用第二种实现方式,则原数组种,在(A,B)之间的元素arrs(A->B) > A, arrs(A->B) > B;同理,原数组中在(B,C)之间的元素都大于B,C;同样,(A,B)之间的元素没有大小关系并且和C没有大小关系,可能有的大于C,可能有的小于C。
4.单调栈的使用技巧
可以将某一时刻在栈中的元素叫做是杆,原数组中两元素之间的元素叫作波(因为2杆之间的元素没有大小关系)。
根据上面3中单调栈的特征,发现两杆要么都小于波,要么都大于波,所以杆波杆显示出一种高低高(V)或者低高低(^)的形式。
下面介绍几种使用技巧,(1)是总的使用技巧,即上面的2种实现的单调栈都可以考虑能不能使用,但是具体使用哪一种单调栈的看具体问题;如果发现一个问题满足(1)。则可以考虑那种单调栈能完成任务,都不能则考虑其他方式。
(2),(3)分别对应2种实现方式的单调栈的使用场景,这些特点只要问题满足,那么一定可以使用对应的单调栈。
(1)总的来说,单调栈适合一些求解过程或者求解出的结果明显的呈现出V或者^形式的问题,这种类型的问题可以考虑使用单调栈。(可以考虑是指,按照这个特点使用单调栈不一定能绝对完成任务),总之能用单调栈解决的问题一定符合v,^,但是符合v,^形式的问题,不一定能用单调栈,的具体问题具体分析(但是大部分都可以)。
例1:leetcode 316题
可以发现该题的示例1和示例2的结果符合按1方式(即进也出)实现单调递增栈的特征。
下面()中为波。
示例1:输出为(bc)abc,可见波是bc,杆是abc,bc>a a<b<c完全符合^形式。
示例2:输出为(cb)acd|(c)b(c),把输出用"|"分成2部分,可以发现前和后部分都符合^形式。
例2:leetcode 456题
可以发现本题就是在找原数组中的^形式,所以可以考虑使用方式1实现的单调递增栈,发现可行。
示例2中 1,4,2就是^形式。可以把1,2看作杆,4看作波
示例3中 -1,3,2等也是^形式。
上面2例子都是,从结果看出满足^形式,下面在举一个从过程中发现V或^形式的例子。
例3:leetcode 402
按示例1来说,在遍历数组过程中时,遍历到3时,可以把4删了,因为3<4,把13xxxxxx肯定比把14xxxxxx小。当遍历到2时,把3删了。所以可以发现,在遍历到3时,[1,4,3]满足^形式(1,3为杆,4为波),在遍历到2时[1,4,3,2]也满足^形式(1,2为杆,4,3为波)。所以此题,我们可以删除k个波即可,这个时刻的栈中元素(即杆)+数组中还未遍历到的元素就组成了该问题的解。
(2)求数组中一个元素的两端最近的大于或者小于该元素的元素(即左边最近的小于/大于它的元素,右边最近的小于/大于它的元素),可以求两端,当然只求一端也可以完成。——适用于第一种实现的单调栈
例子:leetcode 739
可以从题干和例子中,很容易发现,本题就是求每一个元素右边最近的大于它的元素。找到之后,下标相减就是该问题的解。其实很容易发现本题确实符合V形式,
比如我们假如要找上述数组中,75后面第一个大于他的温度即76,则可以把75和76看作杆,71,69,72看作波。[75,71,69,72,76]就符合V形,71的后面第一个大于它的温度是72,[71,69,72]也是V形。
这种类型的问题只能使用第一种单调栈,因为第一种单调栈对于每一个元素都会入栈出栈,在每一个元素入栈时它即是杆(此时栈顶的杆),所以此时前一个杆即要找的第一个在左边比他大或小的元素,在每一个元素出栈时,它即是波,此时栈顶的杆就是右边第一个比它大或者小的元素。所以这种类型的单调栈对于每一个元素都可以找到它的V,^形,即对每一个元素都可以找到它的左右俩端最近的大于它和小于它的值。
(3)将数组中元素按阶梯状分组,使得一个组内的数都(大于/小于)(上一个组/下一个组),求分组数——适合第二种单调栈。
比如一个递增数组[1,2,5,3,4,6,7,9,10,8,11] 则按第二种单调栈,在遍历完数组后,栈为[1,2,5,6,7,10,11]
因为5,4,3大于1,2 。9,10,8都大于7 。所以可以分为1,2,(5,3,4),6,7,(9,10,8),11这几组,下一个组内的数都大于上一个组。
可以发现导致逆序的34可以看作波,[5,3,4,6]就是V形。同样导致逆序的8看作波,[10,8,11]也是V形。所以也符合特点(1)。
例子1:
本题就是将数组按阶梯分组,第一个分组的第一个数和最后一个分组的最后一个数之间的数组成的数组就满题目的解。
例子2:
观察结果可以发现,该问题就可以转化成求阶梯分组数。
该方法只能以数组为单位,题干中求对于每一个元素…,不适合使用该类型的栈。