单调栈、单调队列——挖呀挖呀挖呀挖

        凑个热闹,这几天给学生讲单调栈和单调队列,所以也在挖呀挖。在说为啥挖呀挖之前,先说说这俩是干嘛的:

单调栈:求i位置右侧第一个比arr[i]大或小的值。有趣的是当求右侧第一个i大的值时同时会求出左侧第一个比它大的,小于同样。所以,只需要关心右边就可以了。实际上不仅是大或小,可以求出的是满足条件的第一个。

单调队列:求随动窗口的最大最小值。意思就是说有一个窗口[L,R],随着i++向右移动的时候,这个窗口里面的极值。实际用途多是用来优化DP,使得内循环被去掉。

        然后,细嗦一下单调栈,单调队列你也就理解了。

1、从何而来?

        例题:给定一个数组,对于每一个i求下一个比当前值更大的值(下标或大小都行)。

        思路:遍历一遍,每个位置都向后查找更大的。这需要两重循环,并且很明显内循环会无法利用之前的信息——它是从i位置向后查找的。

        思维:我们朝着把这个朴素算法优化成接近O(n)的方向思考,很容易知道内循环向右是无法利用之前i扫过去的信息的;所以,我们需要换一个思路,思考一下从0到n-1填表有没有必要?很明显是不必要的,问题可以变成在我们的记录表中(空间换时间很普遍吧,要不过去的信息往哪记呢!)记录下最近一个没填的下标x,当arr[i]更大,那我们就可以把ans[x]填上了。

2、细节思考:

        当有若干个没填的怎么办?当然是都记录下来。

        我们要的是最近一个,那么集合特性是什么?LIFO

        当i=n-1时,栈是否可能有剩余,如何处理?不用处理,初始化时ans[]={0}即可。

3、挖呀挖呀挖呀挖:

        当arr[i]大于栈顶,就更新栈顶元素k对应的ans[k],直到栈为空或arr[i]

        思考:栈中的元素从栈顶到栈底具备什么特征?相邻元素中靠近栈底的是前一个更大的。

好了,回顾一下,单调栈的单调性是因为我们需要一个结构记录前面最近一个更小的(后面一个更大的),所以表现出单调,并且从栈顶到栈底的单调性和我们所求方向一致。再更清楚的描述一下我要传递的信息是:单调栈的单调性是因为你的需求、操作而产生的,而不是你要求维护它的什么性质,是你玩单调栈而不是单调栈玩你,好吧?

4、单调队列:

        还是经典例题,滑动窗口的最大值,就是给N个数,求[L,R]下标之间的最大值。

        首先给出结论吧,单调队列的单调性是对队首的竞争而引起的:因为我们要让可能有用的最大值合理保存下来:当arr[i]比队尾的值更大,它就挖呀挖呀挖呀挖。

        思考一下,我们要维护一个定长窗口,所以队列里最多有窗口长度个值,随着i++,队首会过期(即小于当前的L),而窗口内(即队列内)如果存着7、4、1,现在来了个一个6,那么这个6有效期一定比之前的都长,即从现在开始有4、1出场的机会一定要有6出场的机会,而6更大导致4、1没机会出场,被挖呀挖了。

        当6挖到7的时候,它没有能力撼动7的地位,所以只能在后面等着7过期咯。每次挖呀挖呀挖呀挖之后,剔除队首过期值,此时队首即为当前窗口最大值。

        由于我们这种挖呀挖的操作,导致了这个双端队列呈单调性。并且,很遗憾的,它没有什么更特殊的性质,具体结论也不用说了。

        所以,从挖呀挖,不是,这个太上头了这个。所以,数据结构就是菜市场的菜,就是家里的电饭煲煤气灶电烤箱(PQ限制了我的认知,不知道更多了)。当你学会分析问题,那么解决问题时数据结构会自己跳出来让你选的;然后,挖呀挖呀挖呀挖会帮你想起来单调栈是从上往下挖,单调队列是从后往前挖。OK?

你可能感兴趣的:(算法)