1、题目描述
给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值。
进阶:
你能在线性时间复杂度内解决此题吗?
提示:
- 1 <= nums.length <= 10^5
- -10^4 <= nums[i] <= 10^4
- 1 <= k <= nums.length
2、思路
一个普通的队列一定有这两个操作: 一个「单调队列」的操作也差不多:
class Queue {
// enqueue 操作,在队尾加入元素 n
void push(int n);
// dequeue 操作,删除队头元素
void pop();
}
class MonotonicQueue {
// 在队尾添加元素 n
void push(int n);
// 返回当前队列中的最大值
int max();
// 队头元素如果是 n,删除它
void pop(int n);
}
当然,这几个 API 的实现方法肯定跟一般的 Queue 不一样,不过我们暂且不管,而且认为这几个操作的时间复杂度都是 O(1),先把这道「滑动窗口」问题的解答框架搭出来:
int[] maxSlidingWindow(int[] nums, int k) {
MonotonicQueue window = new MonotonicQueue();
List<Integer> res = new ArrayList<>();
for (int i = 0; i < nums.length; i++) {
if (i < k - 1) {
//先把窗口的前 k - 1 填满
window.push(nums[i]);
} else {
// 窗口开始向前滑动
// 移入新元素
window.push(nums[i]);
// 将当前窗口中的最大元素记入结果
res.add(window.max());
// 移出最后的元素
window.pop(nums[i - k + 1]);
}
}
// 将 List 类型转化成 int[] 数组作为返回值
int[] arr = new int[res.size()];
for (int i = 0; i < res.size(); i++) {
arr[i] = res.get(i);
}
return arr;
}
观察滑动窗口的过程就能发现,实现「单调队列」必须使用一种数据结构支持在头部和尾部进行插入和删除,很明显双链表是满足这个条件的。
「单调队列」的核心思路和「单调栈」类似,push方法依然在队尾添加元素,但是要把前面比自己小的元素都删掉:
class MonotonicQueue {
// 双链表,支持头部和尾部增删元素
private LinkedList<Integer> q = new LinkedList<>();
public void push(int n) {
// 将前面小于自己的元素都删除
while (!q.isEmpty() && q.getLast() < n) {
q.pollLast();
}
q.addLast(n);
}
}
你可以想象,加入数字的大小代表人的体重,把前面体重不足的都压扁了,直到遇到更大的量级才停住。
如果每个元素被加入时都这样操作,最终单调队列中的元素大小就会保持一个单调递减的顺序,因此我们的max方法可以可以这样写:
public int max() {
// 队头的元素肯定是最大的
return q.getFirst();
}
pop方法在队头删除元素n,也很好写:
public void pop(int n) {
if (n == q.getFirst()) {
q.pollFirst();
}
}
之所以要判断data.front() == n,是因为我们想删除的队头元素n可能已经被「压扁」了,可能已经不存在了,所以这时候就不用删除了:
至此,单调队列设计完毕,看下完整的解题代码:
class Solution {
LinkedList<Integer> window = new LinkedList<>();
public int[] maxSlidingWindow(int[] nums, int k) {
List<Integer> res = new ArrayList<>();
for(int i = 0; i < nums.length;i++){
if(i < k-1){
push(nums[i]);
}
else{
push(nums[i]);
res.add(window.getFirst());
pop(nums[i-k+1]);
}
}
int[] array = new int[res.size()];
for(int i = 0;i < array.length;i++){
array[i] = res.get(i);
}
return array;
}
public void push(int n){
while(!window.isEmpty() && window.getLast() < n){
window.removeLast();
}
window.add(n);
}
public void pop(int n){
if(window.getFirst() == n){
window.remove();
}
}
public int max(){
return window.getFirst();
}
}