239. 滑动窗口最大值 - 力扣(LeetCode) (leetcode-cn.com)
题目:给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
算法核心:
1.维护一个大小为 K 的队列(数组)头部是 该队列最大的单调队列;
方法:推入元素之前,与该大小为K的队列的 队尾元素 进行比较,如果 推入元素 比 队尾元素 大,则删除 队尾元素,直到大小为 K 的队列被清空或者遇到比推入元素小大的队尾元素。
2.每次窗口向后滑动都要判断:如果窗口移除的元素value等于单调队列的出口元素,那么队列弹出元素,否则不用任何操作。
理解:在队列中元素个数已经为 K 个的情况下,窗口向后移动理应删除队列的队首元素。但是有时候由于单调队列的特殊性,滑动窗口不一定一直是满K个元素的状态。
当队首元素很大,则后入队的元素就会顺利入队。直到队满。
当队首元素很小,为了满足单调队列,后面大的元素会把前面较小的队首元素删掉直到队空或者遇到比自己还大的元素才会入队。这种情况窗口继续向后移动加入新元素并不会超出 K 元素。
代码实现:
var maxSlidingWindow = function (nums, k) {
class MonoQueue {
queue;
constructor() {
this.queue = [];
}
// 向单调队列中添加元素(入队)保证队列的对头元素是该队列中最大的
enqueue(value) {
// 拿出当前队列的队尾元素
let back = this.queue[this.queue.length - 1];
// 若推入的元素比队尾元素大,则删除队尾元素
// 直到队列被清空或者遇到比推入元素小大的队尾元素
while (back != undefined && back < value) {
this.queue.pop();
back = this.queue[this.queue.length - 1];
}
// 跳出循环并推入队列
this.queue.push(value);
}
// 每次窗口滑动都要确认一下原本应该移除的队首元素是否在队列中
// 如果在的话表明队列是满的,需要把该队首元素删除
// 如果不在的话表明队列不是满的,不需要操作
dequeue(value) {
let index = this.front();
if (index === value) {
this.queue.shift();
}
}
// 取出队头元素
front() {
return this.queue[0];
}
}
let helperQueue = new MonoQueue();
// 存储每个窗口最大的元素
let arr = [];
// i 表示每个窗口的队首元素下标
// j 用来循环给定的数组
let i = 0, j = 0;
// 先向单调队列中放入 k 个元素
for (j = 0; j < k; j++) {
helperQueue.enqueue(nums[j]);
}
// 取出第一个滑动窗口的最大值
arr.push(helperQueue.front());
// 窗口向后移动
while (j < nums.length) {
// 推入队列
helperQueue.enqueue(nums[j]);
// 判定原本应该删除的队首元素是否在当前队列中
helperQueue.dequeue(nums[i]);
// 取出当前窗口的最大值
arr.push(helperQueue.front());
// 窗口继续移动
i++; j++;
}
return arr;
};