239. 滑动窗口最大值
题目描述
输入一个整数数组 nums 和窗口长度 k。
一个长为 k 的窗口一开始盖住数组最左边的 k 个数,之后每次向右挪动一格,直到盖住最右边的 k 个数为止。
对窗口的每一个位置,取窗口里的最大值。按窗口从左到右的顺序,返回这些最大值组成的数组。
输入: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。
输入:nums = [1], k = 1 输出:[1]
解释:只有一个窗口 [1]。
数据范围
1 <= nums.length <= 10^5-10^4 <= nums[i] <= 10^41 <= 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 ansclass Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
Deque<Integer> q = new ArrayDeque<>(); int[] ans = new int[nums.length - k + 1];
for (int i = 0; i < nums.length; i++) {
int x = nums[i];
while (!q.isEmpty() && q.peekFirst() <= i - k) q.pollFirst();
while (!q.isEmpty() && nums[q.peekLast()] <= x) q.pollLast();
q.addLast(i);
if (i >= k - 1) ans[i - k + 1] = nums[q.peekFirst()];
}
return ans;
}
}class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
deque<int> q; vector<int> ans;
for (int i = 0; i < (int)nums.size(); i++) {
int x = nums[i];
while (!q.empty() && q.front() <= i - k) q.pop_front();
while (!q.empty() && nums[q.back()] <= x) q.pop_back();
q.push_back(i);
if (i >= k - 1) ans.push_back(nums[q.front()]);
}
return ans;
}
};func maxSlidingWindow(nums []int, k int) []int {
q, ans := []int{}, []int{}
for i, x := range nums {
for len(q) > 0 && q[0] <= i-k {
q = q[1:]
}
for len(q) > 0 && nums[q[len(q)-1]] <= x {
q = q[:len(q)-1]
}
q = append(q, i)
if i >= k-1 {
ans = append(ans, nums[q[0]])
}
}
return ans
}var maxSlidingWindow = function(nums, k) {
const q = [], ans = [];
let head = 0;
for (let i = 0; i < nums.length; i++) {
const x = nums[i];
while (head < q.length && q[head] <= i - k) head++;
while (head < q.length && nums[q[q.length - 1]] <= x) q.pop();
q.push(i);
if (i >= k - 1) ans.push(nums[q[head]]);
}
return ans;
};function maxSlidingWindow(nums: number[], k: number): number[] {
const q: number[] = [], ans: number[] = [];
let head = 0;
for (let i = 0; i < nums.length; i++) {
const x = nums[i];
while (head < q.length && q[head] <= i - k) head++;
while (head < q.length && nums[q[q.length - 1]] <= x) q.pop();
q.push(i);
if (i >= k - 1) ans.push(nums[q[head]]);
}
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 ansclass Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
PriorityQueue<int[]> heap = new PriorityQueue<>((a, b) -> a[0] != b[0] ? b[0] - a[0] : b[1] - a[1]);
int[] ans = new int[nums.length - k + 1];
for (int i = 0; i < nums.length; i++) {
heap.offer(new int[]{nums[i], i});
if (i >= k - 1) {
while (heap.peek()[1] <= i - k) heap.poll();
ans[i - k + 1] = heap.peek()[0];
}
}
return ans;
}
}class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
priority_queue<pair<int, int>> heap;
vector<int> ans;
for (int i = 0; i < (int)nums.size(); i++) {
heap.emplace(nums[i], i);
if (i >= k - 1) {
while (heap.top().second <= i - k) heap.pop();
ans.push_back(heap.top().first);
}
}
return ans;
}
};type pair struct{ val, idx int }
type maxHeap []pair
func (h maxHeap) Len() int { return len(h) }
func (h maxHeap) Less(i, j int) bool { return h[i].val > h[j].val }
func (h maxHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *maxHeap) Push(x interface{}) { *h = append(*h, x.(pair)) }
func (h *maxHeap) Pop() interface{} {
old := *h
x := old[len(old)-1]
*h = old[:len(old)-1]
return x
}
func maxSlidingWindow(nums []int, k int) []int {
h := &maxHeap{}
ans := []int{}
for i, x := range nums {
heap.Push(h, pair{x, i})
if i >= k-1 {
for (*h)[0].idx <= i-k {
heap.Pop(h)
}
ans = append(ans, (*h)[0].val)
}
}
return ans
}var maxSlidingWindow = function(nums, k) {
// 大顶堆,元素为 [值, 下标]
const heap = [];
const less = (a, b) => heap[a][0] > heap[b][0];
const swap = (a, b) => { [heap[a], heap[b]] = [heap[b], heap[a]]; };
const push = (item) => {
heap.push(item);
let i = heap.length - 1;
while (i > 0) {
const p = (i - 1) >> 1;
if (!less(i, p)) break;
swap(i, p);
i = p;
}
};
const pop = () => {
const last = heap.pop();
if (heap.length === 0) return;
heap[0] = last;
let i = 0;
while (true) {
let best = i;
const l = 2 * i + 1, r = 2 * i + 2;
if (l < heap.length && less(l, best)) best = l;
if (r < heap.length && less(r, best)) best = r;
if (best === i) break;
swap(i, best);
i = best;
}
};
const ans = [];
for (let i = 0; i < nums.length; i++) {
push([nums[i], i]);
if (i >= k - 1) {
while (heap[0][1] <= i - k) pop();
ans.push(heap[0][0]);
}
}
return ans;
};function maxSlidingWindow(nums: number[], k: number): number[] {
// 大顶堆,元素为 [值, 下标]
const heap: [number, number][] = [];
const less = (a: number, b: number) => heap[a][0] > heap[b][0];
const swap = (a: number, b: number) => { [heap[a], heap[b]] = [heap[b], heap[a]]; };
const push = (item: [number, number]) => {
heap.push(item);
let i = heap.length - 1;
while (i > 0) {
const p = (i - 1) >> 1;
if (!less(i, p)) break;
swap(i, p);
i = p;
}
};
const pop = () => {
const last = heap.pop()!;
if (heap.length === 0) return;
heap[0] = last;
let i = 0;
while (true) {
let best = i;
const l = 2 * i + 1, r = 2 * i + 2;
if (l < heap.length && less(l, best)) best = l;
if (r < heap.length && less(r, best)) best = r;
if (best === i) break;
swap(i, best);
i = best;
}
};
const ans: number[] = [];
for (let i = 0; i < nums.length; i++) {
push([nums[i], i]);
if (i >= k - 1) {
while (heap[0][1] <= i - k) pop();
ans.push(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)]class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
int[] pre = new int[n], suf = new int[n], ans = new int[n - k + 1];
for (int i = 0; i < n; i++)
pre[i] = i % k == 0 ? nums[i] : Math.max(pre[i - 1], nums[i]);
for (int i = n - 1; i >= 0; i--)
suf[i] = (i == n - 1 || (i + 1) % k == 0) ? nums[i] : Math.max(suf[i + 1], nums[i]);
for (int i = 0; i + k <= n; i++)
ans[i] = Math.max(suf[i], pre[i + k - 1]);
return ans;
}
}class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
int n = nums.size();
vector<int> pre(n), suf(n), ans;
for (int i = 0; i < n; i++)
pre[i] = i % k == 0 ? nums[i] : max(pre[i - 1], nums[i]);
for (int i = n - 1; i >= 0; i--)
suf[i] = (i == n - 1 || (i + 1) % k == 0) ? nums[i] : max(suf[i + 1], nums[i]);
for (int i = 0; i + k <= n; i++)
ans.push_back(max(suf[i], pre[i + k - 1]));
return ans;
}
};func maxSlidingWindow(nums []int, k int) []int {
n := len(nums)
pre, suf := make([]int, n), make([]int, n)
for i := 0; i < n; i++ {
if i%k == 0 {
pre[i] = nums[i]
} else {
pre[i] = max(pre[i-1], nums[i])
}
}
for i := n - 1; i >= 0; i-- {
if i == n-1 || (i+1)%k == 0 {
suf[i] = nums[i]
} else {
suf[i] = max(suf[i+1], nums[i])
}
}
ans := make([]int, 0, n-k+1)
for i := 0; i+k <= n; i++ {
ans = append(ans, max(suf[i], pre[i+k-1]))
}
return ans
}var maxSlidingWindow = function(nums, k) {
const n = nums.length;
const pre = new Array(n), suf = new Array(n);
for (let i = 0; i < n; i++)
pre[i] = i % k === 0 ? nums[i] : Math.max(pre[i - 1], nums[i]);
for (let i = n - 1; i >= 0; i--)
suf[i] = (i === n - 1 || (i + 1) % k === 0) ? nums[i] : Math.max(suf[i + 1], nums[i]);
const ans = [];
for (let i = 0; i + k <= n; i++)
ans.push(Math.max(suf[i], pre[i + k - 1]));
return ans;
};function maxSlidingWindow(nums: number[], k: number): number[] {
const n = nums.length;
const pre: number[] = new Array(n), suf: number[] = new Array(n);
for (let i = 0; i < n; i++)
pre[i] = i % k === 0 ? nums[i] : Math.max(pre[i - 1], nums[i]);
for (let i = n - 1; i >= 0; i--)
suf[i] = (i === n - 1 || (i + 1) % k === 0) ? nums[i] : Math.max(suf[i + 1], nums[i]);
const ans: number[] = [];
for (let i = 0; i + k <= n; i++)
ans.push(Math.max(suf[i], pre[i + k - 1]));
return ans;
}