学算法刷LeetCode【剑指offer专题】: 59 - I. 滑动窗口的最大值

342 阅读2分钟

题目描述

image.png

解题思路

还记得59-II.队列的最大值这道题的解法吗?这道题难度是困难 ,实际上理清思路并不难,无非就是滑动窗口+单调双端队列的结合。我们来一步一步拆解一下:

第一步

总思路:我们准备一个临时存放滑动窗口的数组 window, 然后在这个 window 里拿到最大值,最后将每次求的最大值放的 result 数组中。随着 window 的不断滑动,得到最终结果。

第二步

我们接下来只需要考虑两个问题,一是窗口怎么滑动,二是如何在 window 中获取最大值。

(一)窗口怎么滑动?

  • 遍历 nums 数组,初始化窗口 window,将前 k-1 个元素装进 window, 即当 i < k-1时,window.push(nums[i])

  • i = k时,将 window 的最后一个元素 pushwindow,这时候 window 就是一个包含 k 个元素的数组了,此时就可以去计算 window 中的最大值,并将最大值保存到 result

  • 滑动窗口,窗口前移,将 window 的最后的元素移除(nums[i-k-1]),将新的元素添加到 window,重新计算最大值,直到数组 nums 遍历结束

    let result = [];
   // 具体实现见下方
    let slideWindow = new SlideWindow();
    for(let i = 0; i< nums.length; i++){
        if(i < k-1){
            // 先把窗口前 k - 1 填满
            slideWindow.push(nums[i]);
        }else{
            // 窗口向前滑动,移入新元素
            slideWindow.push(nums[i]);
            result.add(slideWindow.max());
            // 将窗口的后端的元素删除
            slideWindow.shift(nums[i-k+1]);
        }
    }

(二)如何获取 window 中的最大值?

其实就是创建一个单调的双端队列,这个队列里面只放最大值,如果新进的元素比这个队列的最大值小,全部都出队,最后再将这个最大值返回即可。当然,要注意 window 变化时,即 window 出队的时候,需要判断一下出队的值是否是这个最大值,如果是的话,也需要将这个最大值出队,重新计算。

 var SlideWindow = function(){
     // 用于暂存 window 的元素
     this.window = [];
     // 用于存放 window 中的最大值
     this.queue = [];
 }
 
 SlideWindow.prototype.push = function(value){
     this.window.push(value);
     // 计算 存入 queue 中的值, 利用单调双端队列,不符合的值全部出队
     while(this.queue.length && this.queue[0] < value){
         this.queue.pop();
     }
     // 此时,this.queue[0] 即为当前 window 的最大值。
     this.queue.push(value)
 }
 
 SlideWindow.prototype.pop = function(value){
     // 需要判断一下当前值是否在 window 和 queue 中
     if(this.window.length && value === this.window[0]){
         // 存在,删掉
         this.window.shift();
     }
     if(this.queue.length && value === this.queue[0]){
         this.queue.shift();
     }
 }

SlideWindow.prototype.max = function(){
    return this.queue.length ? this.queue[0] : -1;
}

其实,这个 SlideWindow 就是59-II.队列的最大值的解法。

好了,我们把两段代码和起来,就是最终解法啦!

代码

JS


var SlideWindow = function(){
    this.window = [];
    this.queue = [];
}

SlideWindow.prototype.push = function(val){
    while(this.queue.length && this.queue[this.queue.length - 1] < val){
        this.queue.pop();
    }
    this.window.push(val);
    this.queue.push(val);
}

SlideWindow.prototype.max = function() {
    return this.queue.length ? this.queue[0] : -1;
}

SlideWindow.prototype.pop = function(n){
    if(this.window.length && n === this.window[0]){
        this.window.shift();
    }
    if(this.queue。length && n === this.queue[0]){
        this.queue.shift();
    }
    
}

var maxSlidingWindow = function(nums, k) {
    let result = [];
    let slideWindow = new SlideWindow();
    for(let i = 0; i < nums.length; i++){
        if(i < k - 1){
            slideWindow.push(nums[i]);
        }else{
            slideWindow.push(nums[i]);
            result.push(slideWindow.max())
            slideWindow.pop(nums[i-k+1]);
        }
    }
    return result;
};

总结

思路就是滑动窗口+单调双端队列。

时间复杂度: O(n)

空间复杂度: O(k), window 和 queue 中的元素个数不会超过 k 。