在问 · 算法题解

239. 滑动窗口最大值

题目描述

输入一个整数数组 nums 和窗口长度 k。

一个长为 k 的窗口一开始盖住数组最左边的 k 个数,之后每次向右挪动一格,直到盖住最右边的 k 个数为止。

对窗口的每一个位置,取窗口里的最大值。按窗口从左到右的顺序,返回这些最大值组成的数组。

示例 1
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]

解释:六个窗口依次是 [1,3,-1]、[3,-1,-3]、[-1,-3,5]、[-3,5,3]、[5,3,6]、[3,6,7],最大值依次为 3、3、5、5、6、7。

示例 2
输入:nums = [1], k = 1
输出:[1]

解释:只有一个窗口 [1]。

数据范围

  • 1 <= nums.length <= 10^5
  • -10^4 <= nums[i] <= 10^4
  • 1 <= k <= nums.length

解题思路:单调队列

用单调递减双端队列保存候选下标。新值更大时从尾部弹出无用候选;队首过期就弹出,队首始终是窗口最大值。

为什么正确

队列下标递增、对应值递减,且全部属于当前窗口。

复杂度分析

时间 O(n),空间 O(k)。

易错点

队列存下标而不是值,才能判断元素是否已经滑出窗口。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

from collections import deque

class Solution:

    def maxSlidingWindow(self, nums,k):
        q=deque(); ans=[]
        for i,x in enumerate(nums):
            while q and q[0] <= i-k: q.popleft()
            while q and nums[q[-1]] <= x: q.pop()
            q.append(i)
            if i >= k-1: ans.append(nums[q[0]])
        return ans

载入编辑器,自己跑一遍 →

其他解法

堆的做法最直观但是 O(n log n);单调队列只保留可能成为最大值的候选,是 O(n);分块前后缀用预处理把窗口拆成两段,也是 O(n),而且不依赖队列。

解法 2:大顶堆 + 延迟删除 时间 O(n log n),空间 O(n)

把 (值, 下标) 放进大顶堆。每次窗口右移后加入新元素,然后检查堆顶:如果堆顶下标已经不在窗口内就弹出,直到堆顶属于窗口,此时堆顶就是窗口最大值。过期元素只在到达堆顶时才删除。

思路自然,容易想到。堆中可能积累过期元素,空间最坏 O(n),每次操作 O(log n),比单调队列慢。

import heapq

class Solution:
    def maxSlidingWindow(self, nums, k):
        heap = []
        ans = []
        for i, x in enumerate(nums):
            heapq.heappush(heap, (-x, i))
            if i >= k - 1:
                while heap[0][1] <= i - k:
                    heapq.heappop(heap)
                ans.append(-heap[0][0])
        return ans

解法 3:分块前后缀最大值 时间 O(n),空间 O(n)

把数组按长度 k 分块。pre[i] 是从所在块开头到 i 的最大值,suf[i] 是从 i 到所在块结尾的最大值。任何长度为 k 的窗口 [i, i+k-1] 要么恰好是一个块,要么跨过一个块边界,所以答案是 max(suf[i], pre[i+k-1])。

不用任何数据结构,只有两遍线性扫描,同样是 O(n)。需要两个长度为 n 的数组,正确性需要想清楚“窗口最多跨一个边界”。

class Solution:
    def maxSlidingWindow(self, nums, k):
        n = len(nums)
        pre, suf = [0] * n, [0] * n
        for i in range(n):
            pre[i] = nums[i] if i % k == 0 else max(pre[i - 1], nums[i])
        for i in range(n - 1, -1, -1):
            suf[i] = nums[i] if i == n - 1 or (i + 1) % k == 0 else max(suf[i + 1], nums[i])
        return [max(suf[i], pre[i + k - 1]) for i in range(n - k + 1)]

滑动窗口 专题的其他题目

查看 滑动窗口 全部题目 →