算法学习(21):滑动窗口、单调栈

滑动窗口

两个指针,L、R,可以L动也可以R动,L不要超过R,L到R之间是窗口内,R动进,窗口变大,L动出,窗口变小。

[题目]

有一个整型数组arr和一个大小为w的窗口从数组的最左边滑到最右边,窗口每次向右边滑一个位置。
例如,数组为[4,3,5, 4,3,3, 6,7],窗口大小为3时:
[435]43367
4[354]3367
43[543]3 67
435[433]67
4354[336]7
43543[367]
窗口中最大值为5,窗口中最大值为5,窗口中最大值为5,窗口中最大值为4,窗口中最大值为6,窗口中最大值为7
如果数组长度为n,窗口大小为w,则-共产生n-w+1个窗口的最大值。
请实现一个函数。输入: 整型数组arr,窗口大小为w。
输出:一个长度为n-w+1的数组res,res[i]表示每一种窗口状态下的以本题为例,结果应该返回{5,5,5,4,6,7}。


窗口的思想很容易理解,但是要得到窗口内的最大值靠遍历一遍窗口的时间复杂度太高,有没有时间复杂度低的办法。这就引出下面的双端队列

双端队列

双端队列既可以从头进出也可以从尾进出,像上面这道题中的要求,需要这样来进行:

  • 保持双端队列从头到尾是数组中的值从大到小的顺序,队列里储存的是数组的下标,因为下标可以储存更多信息
  • R移动时,从尾部进,进之前先看最尾部储存的下标在数组中对应的的值是否大于当前要进队列的值或者队列是否为空,如果是,则下标进队列,如果不是,则弹出队列尾部的值,直到满足条件
  • L移动时,更新队列,看L左边值的下标,即刚刚出窗口的值的下标是不是当前队列头部储存的下标,如果是,则从头部出队列,如果不是则不更新。

保持以上更新条件,则双端队列的头部储存的下标对应的值就是窗口中最大的值。每个位置上的数最多进出队列1次,划过N个数,时间复杂度是O(N),单次的平均复杂度是O(1)。

上面的题目的解

每次R先往前走三步,然后R与L同时往右走,每次收集一次最大值,最后返回。

如果要求窗口的最小值与上面的思路类似,反过来就行了

单调栈

单调栈解决什么问题

找到数组中每一个位置的数左边离它最近比它最大的数和右边离它最近比它最大的数,要求时间复杂度O(N)。左右边最近的比它小的数同理

当数组中没有重复值时

找左右边最近的比当前值大的数。准备一个栈:

  • 保证从栈底到栈顶保持从大到小的顺序,同样是存下标(因为方便理解,所以下面讨论的都是下标对应的值,实际储存的是下标)
  • 栈为空或当前想要入栈的元素的值比栈顶元素的值小,入栈
  • 想要入栈的元素的值比栈顶元素的值大,弹出栈顶元素,同时生成数据。被弹出的元素左边离它最近比它最大的数就是现在的栈顶,即它弹出前在它下面紧挨着它的元素;被弹出的元素右边离它最近比它最大的数就是让它弹出的数,即当前想要入栈的元素。边弹出边生成数据,直到栈顶元素的值比当前想要入栈的元素的值大,或者栈为空了,入栈。
  • 当数组遍历完了,栈里还留有数据,进入清算阶段。清算阶段弹出的数据都没有右边比它大的,左边离它最近比它大的就是它弹出后的栈顶元素,即弹出前在它下面紧挨着它的元素。最后一个弹出的元素既没有左边比它大的,也没有右边比它大的。

每个数最多进栈一次出栈一次,所以时间复杂度O(N)。

当数组中有重复值时

思路与上面一样,但是栈中存链表(或其他可以将若干个数连起来且能访问最后一个元素的结构),依然储存下标。按照上面没有重复值时的流程走,当遇到栈顶存储的下标在数组中对应的值与当前想进栈的值相同的情况时,把当前值的下标进栈连到与它相同的值的下标的后面去。当遇到要弹出多个相同的值的时候,先弹出下标靠前的,再弹出下标靠后的,并生成数据,左边离它最近比它最大的数是它底下紧挨着的链表中最后一个下标代表的值,右边离它最近比它最大的数就是让它弹出的数。

单调栈的应用

定义:数组中累积和与最小值的乘积,假设叫做指标A。给定一个全是正数的数组,请返回子数组中,指标A最大的值。


流程定成这样:假设子数组必须包含当前数字,且当前数字必须作为子数组的最小值,求出符合要求的子数组的最大的指标A,就是答案
利用单调栈遍历数组,找到每个数字左右两边离它最近比它小的数是扩不到的地方,中间就是所要的符合要求的子数组。把所有数字的符合要求的子数组的指标A都算一遍,然后找出最大的即可。

posted @ 2022-08-01 19:44  小肉包i  阅读(52)  评论(0)    收藏  举报