线性复杂度求解滑动窗口最大值-单调队列实现

最近在leecode上碰到一个很有意思的题目,求解滑动窗口的最大值(https://leetcode-cn.com/problems/sliding-window-maximum/), 题目截图如下:



其实要写出程序解决这个问题很简单啦,但是为啥会被力扣认为是hard难度的题目呢?秘密在于图片的最下方。看到了嘛,数据量达到了10^5次方,这基本说明了一件事情——只有线性复杂度的算法代码能提交通过!

本人实际也想不到线性复杂度的算法来做这个事,然后参考了官方的题解之后明白了,关键在于单调队列。

单调队列,顾名思义是保持单调性的队列。试想若每个窗口的元素我们都有这样一个对应的单调递减队列,那窗口的最大值就是单调队列的第一个元素。

单调队列的实现(以单调递减队列为例),实际与单调栈类似。要添加新元素进入队列时,将其与队尾元素比较,若队尾元素比新元素小,则将队尾元素弹出,直到队尾元素不小于新元素,再将新元素添加进来,成为新的队尾元素。

我的代码如下:

from collections import deque
class Solution:
    def maxSlidingWindow(self, nums, k):
        '''线性复杂度实现滑动窗口最大值-单调递减队列法'''
        # 因为在队列两端都有删除操作,这里我们采用双向队列实现单调队列
        if nums == []:
            return []
        else:
            deq = deque()
            maxlist = []
            #初始操作前k个, 注意加入队列的是索引
            for i in range(k):
                while deq and nums[i] > nums[deq[-1]]: #保证单调性
                    deq.pop()
                deq.append(i)
            maxlist.append(nums[deq[0]]) #单调队列第一个元素即为当前窗口最大值的索引
            #窗口开始滑动,注意此时需要删除上一窗口最左边的元素了
            for i in range(k, len(nums)):
                #注意deq[0]肯定是deq中关于nums列表索引最小的,我们只需要判断其是否是上一窗口最左元素(若不是说明上一窗口最左元素已经被删除)
                if deq and deq[0] == i-k:
                    deq.popleft()
                while deq and nums[i] > nums[deq[-1]]: #保证单调性
                    deq.pop()
                deq.append(i)
                maxlist.append(nums[deq[0]])
        return maxlist
    #线性复杂度,因为列表中每个元素至多补被操作两次,添加一次,弹出一次

因为要队列的队尾既有插入操作也有删除操作,因此我们采用双向队列来实现单调队列,这里直接使用的 Python 中的 collections.deque 方法。

 

算法的其他细节代码中有详细注释,就不再过多解释啦!

posted @ 2020-10-30 20:35  weiyang24  阅读(128)  评论(0)    收藏  举报