Version: Next
239.滑动窗口最大值
难度 困难
给你一个整数数组 nums
,有一个大小为 k
的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k
个数字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值。
You are given an array of integers nums, there is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position.
Return the max sliding window.
示例 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]
示例 3:
输入:nums = [1,-1], k = 1
输出:[1,-1]
示例 4:
输入:nums = [9,11], k = 2
输出:[11]
示例 5:
输入:nums = [4,-2], k = 2
输出:[4]
提示:
1 <= nums.length <= 105
-104 <= nums[i] <= 104
1 <= k <= nums.length
时间复杂度: 要求 O(n)
队列
提示
使用双端队列
- 先确定返回 int[] 的长度,观察一下为
n - k + 1
- 定义两个指针
w
、i
i
:用来扫描数组,定义 i 指向的是 滑动窗口的最后一个元素,因此一开始w
指向的内存区域是非法的w
:指向滑动窗口的第一个元素w
、i
总是一起移动,它们之间的差值是固定的,为k - 1
- 定义一个
双端队列
,用来存储数组中的索引值
- 队列中的索引,对应的
元素值
,从头到尾
是逐渐变小
的- 操作:
- 当队列为空,即添加第一个元素,由
i
指向,直接尾部入队
- 当队列不为空,
i
指向的元素要与队列尾部元素
值进行比较
- while
尾部元素值
<=i 元素值
: 将原尾部元素值
移除,再让i
指向的元素值对应索引尾部入队
(维持 从头到尾 逐渐减小)尾部元素值
>i 元素值
:i
直接尾部入队- 直到
w
开始指向合法内存地址,表明出现第一个滑动窗口
,那么应当 开始更新最终输出数组的值,此时滑动窗口的最大值,就是 双端队列的头元素。(但要检查队列头的索引是否仍然包含在当前滑动窗口的范围中)
- 如果已经不在当前滑动窗口范围内,要移除队列头元素
public class _239滑动窗口最大值 {
public static int[] maxSlidingWindow(int[] nums, int k) {
if (nums == null || nums.length == 0 || k < 1) return new int[0];
if (k == 1) return nums;
// 结果数组
int[] maxArray = new int[nums.length - k + 1];
// 双端队列
Deque<Integer> deque = new LinkedList<>();
for (int i = 0, w = i - k + 1; i < nums.length; i++, w++) {
while (!deque.isEmpty() && nums[deque.getLast()] <= nums[i])
deque.pollLast(); // 不断的从尾部删除较小值
// 直到 尾部元素值 > i 元素值 。 尾部入队
deque.offerLast(i);
if (w >= 0) { // w 开始合法,出现滑动窗口
// 检查队列头是否在滑动窗口内
if (deque.peekFirst() < w || deque.peekFirst() > i)
deque.pollFirst(); // 不在了,就移除它
// 更新结果数组,当前滑动窗口的最大值就是队列头
maxArray[w] = nums[deque.peekFirst()];
}
}
return maxArray;
}
public static void main(String[] args) {
int[] nums = {1, 3, -1, -3, 5, 3, 6, 7};
int[] res = maxSlidingWindow(nums, 3);
for (int m : res) {
System.out.println(m + " ");
}
}
}
为什么这样做
- 整体的目的是什么
- 队头的元素是当前滑动窗口中的最大值
- 让较大的元素尽量向队头靠
- 队头的作用是什么
- 为什么当 nums[队尾] ≤ nums[i] 时,要将队列中的元素删除
简单粗暴好用法
- 两个指针
li
和ri
,li
直接指向 index = 0 的位置,ri = li + k - 1- 一上来就扫描 前k个 元素,找出其中的最大值,记录其
索引
- 移动窗口,只比较新加入的那个元素值,与
先前最大值
比较,决定是否更新最大值- 继续移动,判断先前最大值是否还在窗口中,如果已经不在窗口中了,那么重新扫描整个当前窗口,记录
最大值
- 无脑重复
- 对乱序序列性能还行,对递减序列而言,效率很差,根暴力法差不多