题目
题目来源:力扣(LeetCode)
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值。
示例 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]
思路分析
这道题可以使用单调队列来解决,我们维护一个单调递减的队列,将当前滑窗中的元素下标 依次加入到队列中。
当滑动窗口向右移动时,我们需要把一个新的元素放入队列中。为了保持队列的性质,我们会不断地将新的元素与队尾的元素相比较,如果前者大于等于后者,那么队尾的元素就可以被永久地移除,我们将其弹出队列。我们需要不断地进行此项操作,直到队列为空或者新的元素小于队尾的元素。
由于队列中下标对应的元素是严格单调递减的,因此此时队首下标对应的元素就是滑动窗口中的最大值。但与方法一中相同的是,此时的最大值可能在滑动窗口左边界的左侧,并且随着窗口向右移动,它永远不可能出现在滑动窗口中了。因此我们还需要不断从队首弹出元素,直到队首元素在窗口中为止。
为了可以同时弹出队首和队尾的元素,我们需要使用双端队列。满足这种单调性的双端队列一般称作「单调队列」。
/**
* @param {number[]} nums
* @param {number} k
* @return {number[]}
*/
var maxSlidingWindow = function(nums, k) {
const size = nums.length;
const queue = [];
// 首先找出前 k 个元素中的最大值
for (let i = 0; i < k; i++) {
// 如果队列不为空且当前考察元素大于等于队尾元素,则将队尾元素移除。
// 直到,队列为空或当前考察元素小于新的队尾元素,将当前考察元素添加到队列中
while(queue.length && nums[i] >= nums[queue[queue.length - 1]]) {
queue.pop();
}
// 将元素下标存储到队列中
queue.push(i)
}
// 前 k 个元素中的最大值
const res = [nums[queue[0]]]
// 下标从 k 开始,查找以 k 为大小的窗口内的最大值
for (let i = k; i < size; i++) {
// 如果队列不为空且当前考察元素大于等于队尾元素,则将队尾元素移除。
// 直到,队列为空或当前考察元素小于新的队尾元素,将当前考察元素添加到队列中
while(queue.length && nums[i] >= nums[queue[queue.length -1]]) {
queue.pop()
}
// 将元素下标存储到队列中
queue.push(i);
// 当队首元素的下标小于滑动窗口左侧边界left时
// 表示队首元素已经不在滑动窗口内,因此将其从队首移除
while (queue[0] <= i - k) {
queue.shift()
}
// 队首元素是该窗口内的最大值
res.push(nums[queue[0]])
}
return res
};