239. 滑动窗口最大值
LeetCode题目要求
给你一个整数数组 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
解题思路
本题有些难度,如果不考虑时间复杂度,可以通过队列方式。大致思路是,队列中始终保持 3 个元素,满 3 个后找到最大值放入结果集,由于是队列,最大值不一定在最前方,那么需要遍历找到对应的值 poll 出来,而过程中其他的元素便于二次使用需要再次入队;然后再把对头元素 pool 出来。重复过程知道所有元素都遍历完成。
参考 carl 的自定义单调队列,可以简化滑动窗口的操作过程。具体直接看代码中的注释说明吧。
上代码
class MyQueue {
// 自定义的队列实现,通过双端队列实现
// 包含了 poll 头元素方法;offer 放入元素方法;getMax 方法即取队列头元素
Deque<Integer> deque = new LinkedList<>();
/**
* poll(int val) 方法根据给定的元素,如果与队列头元素相等,那么出队
*/
public void poll(int val) {
if (!deque.isEmpty() && val == deque.peek()) {
deque.poll();
}
}
/**
* offer(int val) 入队给定的元素,当给定值大于队尾元素,那么将队尾所有小于给定值的元素全部移除队列,即队内保证单调递减元素存在; 亦可理解为,如果队列中已存在的元素值没有新元素值大,那么就把他们抛弃,如果新元素值小于队列内元素值时才放入。保证队列单调递减
*/
public void offer(int val) {
while (!deque.isEmpty() && val > deque.peekLast()) {
deque.removeLast();
}
deque.offer(val);
}
/**
* 获取队列最大值,由于队列保持的单调递减存储,那么获取最大值,即取对头元素
*/
public int getMax() {
return deque.peek();
}
}
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
MyQueue q = new MyQueue();
// 先将给定 k 个元素放入自定义队列中
for (int i = 0; i < k; i++) {
q.offer(nums[i]);
}
// 计算返回结果的数组长度,用数组长度 - 滑动窗口长度 + 1
int len = nums.length - k + 1;
// rr 数组用于存储结果
int[] rr = new int[len];
// rr 数组下标索引
int rrIndex = 0;
// 由于 q 内进行了一个窗口的操作,这里第一次先取出第一个最大值放入结果数组 rr
rr[rrIndex++] = q.getMax();
// 从 k 位置开始,遍历 nums 数组,逐个放入 q
for (int i = k; i < nums.length; i++) {
// 移除窗口内的最前面的元素,移除一个
q.poll(nums[i - k]);
// 将当前元素放入窗口,并做 deque 的 offer 操作
q.offer(nums[i]);
// 以上操作后,又保证了当前窗口内的最大值已经在 单调队列的 第一个了。将最大值放入结果 rr
rr[rrIndex++] = q.getMax();
}
return rr;
}
}
重难点
掌握队列的操作,尤其是双端队列对于元素的操作。同时自定义的队列是一个单调递减的队列,通过三个方法完成了每次可以取到窗口中最大值的过程。
附:学习资料链接