滑动窗口最大值

239. 滑动窗口最大值

题目描述

给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。
你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 
返回滑动窗口中的最大值。 

进阶: 
你能在线性时间复杂度内解决此题吗? 

 示例: 
输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7] 
解释: 
滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7 

提示: 
1 <= nums.length <= 10^5 
-10^4 <= nums[i] <= 10^4 
1 <= k <= nums.length 

Related Topics 堆 Sliding Window 

提到滑动窗口,首先便想到了滑动窗口算法,所以我们第一步便尝试使用滑动窗口来解决这个问题。
首先回顾下滑动窗口算法框架:

left, right = 0, 0
windown = []
while right < len(nums):
    # 增大窗口
    windown.append(nums[right])
    right += 1
    while (windown need to shrink):
        windown.remove(nums[left])
        left += 1

根据此框架,不难写出如下算法:

class Solution:
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        n = len(nums)
        left, right = 0, k-1
        res = []
        while right < n:
            right += 1
            if right - left > k:
                left += 1
            maxValue = float('-inf')
            for i in range(left, right):
                maxValue = max(maxValue, nums[i])
            res.append(maxValue)
        return res

算法复杂度为 O(nk),提交显示超时。我们的重复计算主要发生在内循环,计算窗口内的最大值处。我们尝试对这个过程进行优化。我们知道大顶堆的堆顶即是最大值,那我们是不是可以将窗口内的数值维护成一个大顶堆呢?这样每次我们只需要获得堆顶元素的值。维护一个大小为

的堆复杂度为
,看来还是可行的。但问题是当我们需要从堆中弹出元素时,即堆中元素个数超过
,该怎样操作呢?直接弹出 nums[left] 如果有重复元素呢?其实我们可以将每个元素及其索引一同存进堆中,这样只要当堆顶元素的索引和当前数据的索引差值大于等于
,则需要将堆顶元素弹出了。

import heapq # 小顶堆
class Solution:
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        n = len(nums)
        heap = []
        res = []
        for i in range(n):
            while heap and i - heapq.nsmallest(1, heap)[0][1] >= k:
                heapq.heappop(heap)
            heapq.heappush(heap, [-nums[i], i])
            if i+1 >= k:
                num, index = heapq.nsmallest(1, heap)[0]
                res.append(-num)
        return res

算法复杂度为 O(nlogk),提交依然显示超时。那我们还能将复杂度优化到

吗?
我们仔细分析上面用堆实现的算法,其实我们关心的只是堆顶元素。只有当堆中元素需要移除时,我们才需要重新堆化:将下一个最大值放到堆顶位置。于是我们可以构建一个有序队列,第1个位置适中保持当前窗口的最大值,当需要加入新值时,第一个值和当前值之间的比当前值小的元素均可以舍弃,因为此时的新值是能在窗口中保持最长时间的值。什么时候需要移除最大值呢——当最大值应当滑出窗口的时候。所以最后我们采用一个记录索引的列表 indexs:

class Solution:
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        n = len(nums)
        indexs = []
        res = []
        for i in range(n):
            if indexs and i-indexs[0] >= k:
                indexs.pop(0)
            while indexs and nums[indexs[-1]] < nums[i]:
                indexs.pop()
            indexs.append(i)
            if i >= k-1:
                res.append(nums[indexs[0]])
        return res

因为每个元素只有一次加入和一次移除操作,所以时间复杂度为

你可能感兴趣的:(滑动窗口最大值)