题目
给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k个数字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值。
思路
单调队列的思想
单调队列:又名双端队列,具有一定单调性的队列(单调递增,单调递增),可以从队列的首尾进出元素
只维护可能成为最大值的元素
简单来说,就是,保证队列出口是最大的元素,队列中前面的元素比后面进来的元素小的话就全部挤掉,后面进来的元素比前面的元素小的话就保留
比如对于窗口{2,3,5,1,4}
中的元素,单调队列只需要维护{5,4}
就够了,其中2
和3从队头出队列了,1
从队尾出队列了,队列中只留下5,4
,保证单调队列里单调递减,此时队列出口元素就是窗口最大元素
(1)deque
内 仅包含窗口内的元素 ⇒ 每轮窗口滑动移除了元素 nums[i−1]
,需将 deque
内的对应元素一起删除。
(2)deque
内的元素 非严格递减 ⇒ 每轮窗口滑动添加了元素 nums[j+1]
,需将 deque
内所有 <nums[j+1]
的元素删除。
算法流程:
(1)初始化: 双端队列 deque
,结果列表 res
,数组长度 n
;
(2)滑动窗口: 左边界范围 i∈[1−k,n−k]
,右边界范围 j∈[0,n−1]
;
如下图所示:
a)若 i>0
且 队首元素 deque[0]
= 被删除元素 nums[i−1]
(即滑出窗口的元素):则队首元素出队;
b)删除 deque
内所有 <nums[j]
的元素,以保持 deque
递减;
c)将 nums[j]
添加至 deque
尾部;
d)若已形成窗口(即 i ≥ 0
,所谓形成窗口就是):将窗口最大值(即队首元素deque[0]
)添加至列表 res
;
(3)返回值: 返回结果列表 res
;
简单来说就是,不断入队列,只要我比你大,你给我出去;我比你小,就跟在你后面,最终保持递减(这里要注意,1
是从队尾出的,图画的有点问题,4
是比较还没入队列,先出1
在入4
的):
java代码如下:
//要注意,是先更新窗口,然后根据弹出窗口的值,和新加入窗口的值,再去更新单调队列中的值
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if(nums.length == 0 || k == 0)
return new int[0];
Deque<Integer> deque = new LinkedList<>();
int[] res = new int[nums.length - k + 1];//数组 nums的长度为 n ,则共有 (n−k+1) 个窗口
for(int i = 1 - k, j = 0; j < nums.length; i++, j++) {//窗口开始滑动,最开始的范围为[1-k,0]
// 因为每轮窗口滑动移除的元素为 nums[i−1](即窗口的左边界nums[i]的前一个元素),需将 deque 内的对应元素一起删除,即删除 deque 中对应的 nums[i-1]
if(i > 0 && deque.peekFirst() == nums[i - 1])
deque.removeFirst();
// 保持 deque 递减,nums[j]为窗口右边界,即加入窗口的新元素
while(!deque.isEmpty() && deque.peekLast() < nums[j])//如果当前队列的队尾小于要加入的新元素
deque.removeLast();//则删除队尾元素,维持队列递减
deque.addLast(nums[j]);//加入新元素
// 记录窗口最大值
if(i >= 0)//当形成窗口时
res[i] = deque.peekFirst();//将窗口最大值,即队首元素 deque[0](因为是递减),添加至列表res
}
return res;
}
}