--- comments: true difficulty: 困难 edit_url: https://github.com/doocs/leetcode/edit/main/solution/0200-0299/0239.Sliding%20Window%20Maximum/README.md tags: - 队列 - 数组 - 滑动窗口 - 单调队列 - 堆(优先队列) --- # [239. 滑动窗口最大值](https://leetcode.cn/problems/sliding-window-maximum) [English Version](/solution/0200-0299/0239.Sliding%20Window%20Maximum/README_EN.md) ## 题目描述

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

返回 滑动窗口中的最大值

 

示例 1:

输入: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

示例 2:

输入:nums = [1], k = 1
输出:[1]

 

提示:

## 解法 ### 方法一:优先队列(大根堆) 我们可以使用优先队列(大根堆)来维护滑动窗口中的最大值。 先将前 $k-1$ 个元素加入优先队列,接下来从第 $k$ 个元素开始,将新元素加入优先队列,同时判断堆顶元素是否滑出窗口,如果滑出窗口则将堆顶元素弹出。然后我们将堆顶元素加入结果数组。 时间复杂度 $O(n \times \log k)$,空间复杂度 $O(k)$。其中 $n$ 为数组长度。 #### Python3 ```python class Solution: def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]: q = [(-v, i) for i, v in enumerate(nums[: k - 1])] heapify(q) ans = [] for i in range(k - 1, len(nums)): heappush(q, (-nums[i], i)) while q[0][1] <= i - k: heappop(q) ans.append(-q[0][0]) return ans ``` #### Java ```java class Solution { public int[] maxSlidingWindow(int[] nums, int k) { PriorityQueue q = new PriorityQueue<>((a, b) -> a[0] == b[0] ? a[1] - b[1] : b[0] - a[0]); int n = nums.length; for (int i = 0; i < k - 1; ++i) { q.offer(new int[] {nums[i], i}); } int[] ans = new int[n - k + 1]; for (int i = k - 1, j = 0; i < n; ++i) { q.offer(new int[] {nums[i], i}); while (q.peek()[1] <= i - k) { q.poll(); } ans[j++] = q.peek()[0]; } return ans; } } ``` #### C++ ```cpp class Solution { public: vector maxSlidingWindow(vector& nums, int k) { priority_queue> q; int n = nums.size(); for (int i = 0; i < k - 1; ++i) { q.push({nums[i], -i}); } vector ans; for (int i = k - 1; i < n; ++i) { q.push({nums[i], -i}); while (-q.top().second <= i - k) { q.pop(); } ans.emplace_back(q.top().first); } return ans; } }; ``` #### Go ```go func maxSlidingWindow(nums []int, k int) (ans []int) { q := hp{} for i, v := range nums[:k-1] { heap.Push(&q, pair{v, i}) } for i := k - 1; i < len(nums); i++ { heap.Push(&q, pair{nums[i], i}) for q[0].i <= i-k { heap.Pop(&q) } ans = append(ans, q[0].v) } return } type pair struct{ v, i int } type hp []pair func (h hp) Len() int { return len(h) } func (h hp) Less(i, j int) bool { a, b := h[i], h[j] return a.v > b.v || (a.v == b.v && i < j) } func (h hp) Swap(i, j int) { h[i], h[j] = h[j], h[i] } func (h *hp) Push(v any) { *h = append(*h, v.(pair)) } func (h *hp) Pop() any { a := *h; v := a[len(a)-1]; *h = a[:len(a)-1]; return v } ``` ### 方法二:单调队列 求滑动窗口的最大值,一种常见的方法是使用单调队列。 我们可以维护一个从队头到队尾单调递减的队列 $q$,队列中存储的是元素的下标。遍历数组 $\textit{nums}$,对于当前元素 $\textit{nums}[i]$,我们首先判断队头元素是否滑出窗口,如果滑出窗口则将队头元素弹出。然后我们将当前元素 $\textit{nums}[i]$ 从队尾开始依次与队尾元素比较,如果队尾元素小于等于当前元素,则将队尾元素弹出,直到队尾元素大于当前元素或者队列为空。然后将当前元素的下标加入队列。此时队列的队头元素即为当前滑动窗口的最大值,注意,我们将队头元素加入结果数组的时机是当下标 $i$ 大于等于 $k-1$ 时。 时间复杂度 $O(n)$,空间复杂度 $O(k)$。其中 $n$ 为数组 $\textit{nums}$ 的长度。 #### Python3 ```python class Solution: def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]: q = deque() ans = [] for i, x in enumerate(nums): if q and i - q[0] >= 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 ``` #### Java ```java class Solution { public int[] maxSlidingWindow(int[] nums, int k) { int n = nums.length; int[] ans = new int[n - k + 1]; Deque q = new ArrayDeque<>(); for (int i = 0; i < n; ++i) { if (!q.isEmpty() && i - q.peekFirst() >= k) { q.pollFirst(); } while (!q.isEmpty() && nums[q.peekLast()] <= nums[i]) { q.pollLast(); } q.offerLast(i); if (i >= k - 1) { ans[i - k + 1] = nums[q.peekFirst()]; } } return ans; } } ``` #### C++ ```cpp class Solution { public: vector maxSlidingWindow(vector& nums, int k) { deque q; vector ans; for (int i = 0; i < nums.size(); ++i) { if (q.size() && i - q.front() >= k) { q.pop_front(); } while (q.size() && nums[q.back()] <= nums[i]) { q.pop_back(); } q.push_back(i); if (i >= k - 1) { ans.push_back(nums[q.front()]); } } return ans; } }; ``` #### Go ```go func maxSlidingWindow(nums []int, k int) (ans []int) { q := []int{} for i, x := range nums { if len(q) > 0 && i-q[0] >= 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 } ``` #### TypeScript ```ts function maxSlidingWindow(nums: number[], k: number): number[] { const ans: number[] = []; const q = new Deque(); for (let i = 0; i < nums.length; ++i) { if (!q.isEmpty() && i - q.front()! >= k) { q.popFront(); } while (!q.isEmpty() && nums[q.back()!] <= nums[i]) { q.popBack(); } q.pushBack(i); if (i >= k - 1) { ans.push(nums[q.front()!]); } } return ans; } ``` #### Rust ```rust use std::collections::VecDeque; impl Solution { pub fn max_sliding_window(nums: Vec, k: i32) -> Vec { let k = k as usize; let mut ans = Vec::new(); let mut q: VecDeque = VecDeque::new(); for i in 0..nums.len() { if let Some(&front) = q.front() { if i >= front + k { q.pop_front(); } } while let Some(&back) = q.back() { if nums[back] <= nums[i] { q.pop_back(); } else { break; } } q.push_back(i); if i >= k - 1 { ans.push(nums[*q.front().unwrap()]); } } ans } } ``` #### JavaScript ```js /** * @param {number[]} nums * @param {number} k * @return {number[]} */ var maxSlidingWindow = function (nums, k) { const ans = []; const q = new Deque(); for (let i = 0; i < nums.length; ++i) { if (!q.isEmpty() && i - q.front() >= k) { q.popFront(); } while (!q.isEmpty() && nums[q.back()] <= nums[i]) { q.popBack(); } q.pushBack(i); if (i >= k - 1) { ans.push(nums[q.front()]); } } return ans; }; ```