题目
给你一个整数数组 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]
提示:
1 <= nums.length <= 105
-104 <= nums[i] <= 104
1 <= k <= nums.length
思路
本题解采用「双向队列」的方法来解决。首先我们需要明确的是,对于一个窗口中的数,它的最大值只可能有以下几种情况:
- 数组中的最大值在当前窗口中。
- 数组中的最大值不在当前窗口中,但它之前的数在当前窗口中。
- 数组中的最大值不在当前窗口中,也不在当前窗口之前的数中。
我们可以使用「双向队列」来维护这些情况的变化,在每个窗口滑动时,我们将窗口中的数从队列末尾插入,然后不断从队列头部删除不在当前窗口中的数。同时,我们需要维护队列中的数是按照从大到小的顺序排列的,这样队列头部的数就是当前窗口的最大值。
算法具体实现见下方代码段。
代码实现
function maxSlidingWindow(nums: number[], k: number): number[] {
const res = [];
const queue = [];// 存的是索引
for(let i = 0; i < nums.length; i++) {
// 如果队列里面第一个元素(索引)小于队左端点的索引(i - k + 1)说明已经出队了,需要删除
// 由于队列是单调递减的,而不是按顺序排列的,所以需要从左到右遍历
while(queue.length && queue[0] < i - k + 1) {
queue.shift();// 队首元素出队
}
// 为了保证有顺序,需要将小于当前元素的值都删掉,因为比当前值小的只没有当前值有用了
while(queue.length && nums[queue[queue.length - 1]] < nums[i]) {
queue.pop();
}
// 当前元素有用,加到后面
queue.push(i);
// 从k-1 ~ nums.length -1 queue的长度都是k,都是有效的,把结果存下来
if(i >= k - 1) {
res.push(nums[queue[0]]);
}
}
return res;
};
- 时间复杂度:O(n),其中 n 是数组的长度。每个数只会入队和出队一次,因此时间复杂度为 O(n)。
- 空间复杂度:O(k),其中 k 是滑动窗口的大小。使用双向队列存储滑动窗口的索引,因此空间复杂度是 O(k)。
理解
新增、获取最大值
想象有这样的一个queue,queue内的数据是单调递减的。那么,我们只需要获取queue的front就能获取到最大的元素。
我们使用这样的queue来维护滑动窗口中的元素。每次滑动窗口的时候,新增元素、删除元素,并维护queue内元素的顺序。
但是,我们其实不需要把滑动窗口内的所有元素都维护进这个queue,因为我们找的其实是滑动窗口中的最大值。比如,窗口-1, [2, 4, 1], 0, 2对应的queue中,就不需要有2,queue中的内容为[4, 1]。虽然1比4小,但是1可能是后续窗口的最大值。
总结一下。因为每次向queue中新增元素的时候,queue中已有元素在数组中的位置肯定比当前元素更靠前。所以,如果queue中已有的一些元素,它们的值小于新增的元素,则它们肯定不会成为当前窗口(及后续窗口)中的最大值。所以我们就可以把这些元素pop掉了。
每个新增的元素都是经过如上判断及操作的话,那么这个queue就是一个单调递减的queue了。留在queue中的元素是:1.当前窗口中的最大值;2.后续窗口中可能的最大值。
删除
当窗口滑过了元素的时候,就应该删除该元素。
经过前面的分析,我们知道如果滑出窗口的元素不是queue的max value的话,那说明窗口中该元素之后肯定有更大的元素成为了max value。那么这种情况下,“滑出窗口的元素”肯定已经在这个max value的元素加入到queue的时候已经被pop了。
所以我们只需要判断max value是否等于“滑出窗口的元素”,如果等于则pop front。