【题目】
【方法一:单调队列】
单调队列知识: 我们这里用单调队列来解决。我们前面学过单调栈了。再来学单调队列就很简单。
比如为维护了一个从大到小的单调栈,元素是9,7,4。当新来一个元素5的时候,就跟队尾的元素比较一下,如果小于队尾元素,就直接入队。如果大于队尾元素,就先让队尾元素出队,直到遇到大的元素或者队为空。 我们可以根据前面单调栈的代码,轻松写出如下代码:
for(int i = 0 ;i<n;i++){ while(队不为空 && 队尾元素<nums[i]){ 队尾元素出队; } 当前元素进队; }
你发现了没有,如果把前面的出口堵上,这就是一个单调栈。 所以,单调队列跟单调栈差不多,如果不对队头进行操作的话。里面存放的是元素是,当前还没有在它右边找到更大值的那些元素。 也正是利用这一个性质,我们结合队列特有的出队操作,解决了当前窗口的最大值问题。后面详细讲。
说明: 因为需要队尾元素出来,所以这个单调队列不能是一般的队列,需要用双端队列来实现。 关于双端队列的基础知识,也很简单,可以看我的博客:https://blog.youkuaiyun.com/pengchengliu/article/details/91047320
题目分析: 上面说了,队列中维护的是当前还没有找到右边较大值的哪些结点。也就是从0到i的元素中,没有找到它右边比它更大的那些元素。 比如: 5 4 2 1 3 当i=3操作后,队内的元素为5,4,3。对应的下标是0,1,4。意味着从第0个数开始到第4个数结束没有比5更大的元素、从第1个数开始到第4个数结束没有比4更大的元素,从第4个数开始到第4个数结束没有比3更大的元素。 而我们要求的是窗口(假设为3)内最大的数。也就是从2开始到4结束最大的数。 我们只需要将队列前面下标<2的元素移除,然后剩下的队头元素就是答案。
举例: num[] : 5 4 2 1 3 -1 -3 5 3 窗口大小k为3。 下 标 : 0 1 2 3 4 5 6 7 8 ret[]下标: 0 1 2 3 4 5 6
我们维护一个单调递减的队列。 (1)当i = 0时,5进队: (2)当i=1时,4比5要小,所以4进队: (3)当i=2时,2比4要小,所以2进队: 此时i刚好走完第一个窗口,所以ret[0]=队首元素=5。
(4)当i=3时,1比2小,1进队: 同时看一下队首元素有没有小于i-(k-1)。队首元素下标为0,小于2。所以把5移除。 此时,窗口的最大值为队首元素4。ret[1]=4。
(5)当i=4时,3大于1,1出队;3大于2,2出队。3小于4,3入队。 下标为4的时候,窗口为2,3,4。而队首下标为1<2,所以4移除。 当前最大值为ret[2]=3.
(6)当i=5时,-1入队。 队首下标没超。 ret[3]=3.
(7)当i=6时,-3入队。 队首下标没超。 ret[4]=3.
(8)当i=7时,5>-3,-3出队;5>-1,-1出队;5>3,3出队。空了,5入队。 队首下标没有超。 ret[5]=5.
(9)当i=8时,3进队。 队首下标没超。 ret[6]=5.
代码:
结果:
结果不算太好,但是这确实是本题的最经典的方法。 如果没有看懂的话,建议再看看剑指offer的解答(对应59题)。
【方法二】 其实也不需要这么复杂。一共就分2种情况:当max的值在最左边,当max的值不在最左边。 (1)max的值在最左边,也就是下一次移动的时候max将要被移除窗口外时。 如果max<i,那太好了,max就等于i就行了。 如果max>i,那就必须要i与前面的k-1个数都比较一遍了。 (2)max不在最左边,意味着当i加入时,max不移除。 如果max>i,太好了,不动,max还是max。 如果max<i,那也不错。只需要将max更新为i即可。 写代码的时候,可以把第1个如果和第4个如果合并。
代码:
结果:
|