239. 滑动窗口最大值
困难
给你一个整数数组 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] <= 1041 <= k <= nums.length
双指针+遍历 超时算法但思路清晰 O(kn)
function maxSlidingWindow(nums: number[], k: number): number[] {
const len = nums.length
const res: number[] = []
let left = 0
let right = k-1
while(right < len){
// 计算当前窗口内的最大值
const max = calMax(nums,left,right) as number
res.push(max);
left ++
right++
}
return res
};
// 这个函数用来计算最大值
function calMax(arr: number[],left: number,right: number): number {
let maxNum = arr[left]
for(let i=left;i<=right;i++){
if(arr[i]> maxNum){
maxNum = arr[i]
}
}
return maxNum
}
思路分析:双端队列法 O(n)
使用双端队列法,核心的思路是维护一个有效的递减队列。
每尝试推入一个元素前,都把这个元素与队列尾部的元素作对比。根据对比结果的不同,采取不同的措施:
- 如果试图推入的元素(当前元素)大于队尾元素,则意味着队列的递减趋势被打破了。此时我们需要将队列尾部的元素依次出队(注意由于是双端队列,所以队尾出队是没有问题的)、直到队尾元素大于等于当前元素为止,此时再将当前元素入队。
- 如果试图推入的元素小于队列尾部的元素,那么就不需要额外的操作,直接把当前元素入队即可。
接下来,每往前遍历一个元素,都需要重复以上的几个步骤。这里我总结一下每一步都做了什么:
- 检查队尾元素,看是不是都满足大于等于当前元素的条件。如果是的话,直接将当前元素入队。否则,将队尾元素逐个出队、直到队尾元素大于等于当前元素为止。
- 将当前元素入队
- 检查队头元素,看队头元素是否已经被排除在滑动窗口的范围之外了。如果是,则将队头元素出队。
- 判断滑动窗口的状态:看当前遍历过的元素个数是否小于
k。如果元素个数小于k,这意味着第一个滑动窗口内的元素都还没遍历完、第一个最大值还没出现,此时我们还不能动结果数组,只能继续更新队列;如果元素个数大于等于k,这意味着滑动窗口的最大值已经出现了,此时每遍历到一个新元素(也就是滑动窗口每往前走一步)都要及时地往结果数组里添加当前滑动窗口对应的最大值(最大值就是此时此刻双端队列的队头元素)。
这四个步骤分别有以下的目的:
- 维持队列的递减性:确保队头元素是当前滑动窗口的最大值。这样我们每次取最大值时,直接取队头元素即可。
- 这一步没啥好说的,就是在维持队列递减性的基础上、更新队列的内容。
- 维持队列的有效性:确保队列里所有的元素都在滑动窗口圈定的范围以内。
- 排除掉滑动窗口还没有初始化完成、第一个最大值还没有出现的特殊情况。
结合以上的分析,我们来写代码:
function maxSlidingWindow(nums: number[], k: number): number[] {
const len: number = nums.length;
const res: number[] = [];
const deque: number[] = [];
for (let i = 0; i < len; i++) {
//当队尾小于当前元素,将队尾元素(索引)不断出队,直至队尾元素大于等于当前元素
while (deque.length && nums[deque[deque.length - 1]] <= nums[i]) {
deque.pop();
}
// 入队当前元素索引(注意是索引)
deque.push(i);
// 当队头元素的索引已经被排除在滑动窗口之外时 将队头元素索引出队
while (deque.length && deque[0] <= i - k) {
deque.shift();
}
// 判断滑动窗口的状态,只有在被遍历的元素个数大于 k 的时候,才更新结果数组
if (i >= k - 1) {
res.push(nums[deque[0]]);
}
//每轮结果
// [ 0 ]
// [ 1 ]
// [ 1, 2 ]
// [ 1, 2, 3 ]
// [ 4 ]
// [ 4, 5 ]
// [ 6 ]
// [ 7 ]
}
return res;
}