js算法题解(第十八天)----剑指 Offer 59 - II. 队列的最大值和239. 滑动窗口最大值

157 阅读5分钟

「这是我参与2022首次更文挑战的第19天,活动详情查看:2022首次更文挑战

前言

每天至少一道算法题,死磕算法

今天我们再把队列深入一下,以前我们做过栈中的最小值,现在我们来做队列中的最大值

做这种栈中的最小值,队列中的最大值这种题目,你就记住如果是最小值那么辅助数组是升序排列,如果是最大值那么辅助数组是降序排列,这是解这种题最牛逼的思路。因为这个辅助数组只用栈或者队列的操作是完不成的,所以我们通常使用双端队列,也就是push,pop,unshift,shift这些操作都能使用,所以这种解题方法也称为双端队列法,我们一般定义这个数组为deque,也就是双端队列

今天的这两道题非常相似,一道是剑指 Offer 59 - II. 队列的最大值中等难度的,一道是239. 滑动窗口最大值困难(是不是超兴奋)难度的

题目

我们先来看剑指 Offer 59 - II. 队列的最大值

请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。

若队列为空,pop_front 和 max_value 需要返回 -1

示例 1:

输入: 
["MaxQueue","push_back","push_back","max_value","pop_front","max_value"]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]

示例 2:

输入: 
["MaxQueue","pop_front","max_value"]
[[],[],[]]
输出: [null,-1,-1]

思路

第一步,从题目中提取关键字

  • 1.完成一个队列,有一些基本操作,最主要的是写一个获取最大值函数
  • 2.若队列为空,pop_front 和 max_value 需要返回 -1

第二步,分析

  • 1.题中需要获取最大值,我们总不能用Math.max(),虽然也能实现,但是没有算法思想可言,面试是拿不出手的。所以只用一个队列是实现不了这个功能的,所以记得我们做最小栈题目的时候说的,这种一般都需要辅助数组,用来存取最大值,那么问题来了如何往这个辅助数组里面存取数值么,什么时候push,什么时候shift(因为题目是队列,所以是shift操作)呢
  • 2.辅助数组如何push?我们上面说了,最大值问题要降序排列
    • 如果当前辅助数组中没有值的话,那么push
    • 如果当前数组中有值
      • 当要push的值比辅助数组中最后一位小,那么直接push
      • 当要push的值比辅助数组中最后一位大,那么就不符合降序排列了,我们把不符合条件的数值pop出去,直到辅助数组中最后一个数大于要插入进来的值,那么就push 我们优化一下,优化成什么时候不需要push,也就是当数组中有值,并且当要push的值比辅助数组中最后一位大的时候不需要push,此时我们需要while循环,把辅助数组中比要插入的数小的数都pop出去
  • 3.辅助数组什么时候shift? 如果要出队列的值和辅助数组的第一位数相等的话,那么就可以出列

题解

var MaxQueue = function() {
    // 队列
    this.queue = [];
    // 辅助数组,双端队列,deque
    this.deque = [];
};

/**
 * @return {number}
 */
MaxQueue.prototype.max_value = function() {
    // 如果队列中没有值的话,那么返回-1
    if(!this.queue.length){
        return -1;
    }
    // 因为我们的deque是递减的,所以只要返回第一位就可以了
    return this.deque[0];
};

/** 
 * @param {number} value
 * @return {void}
 */
MaxQueue.prototype.push_back = function(value) {
    // queue正常push
    this.queue.push(value);
    // 当数组中有值,并且当要push的值比辅助数组中最后一位大的时候不需要push,此时我们需要while循环,把辅助数组中比要插入的数小的数都pop出去
    while(this.deque.length&&value>this.deque[this.deque.length-1]){
        this.deque.pop();
    }
    // 其他时候都需要push
    this.deque.push(value);
    
};

/**
 * @return {number}
 */
MaxQueue.prototype.pop_front = function() {
    // 队列中无值返回-1
    if(!this.queue.length){
        return -1;
    }
     
    let value = this.queue.shift();
     // 如果要出队列的值和辅助数组的第一位数相等的话,那么就可以出列
    if(this.deque[0]===value){
        this.deque.shift();
    }
    return value;
};

接下来,我们在来看一道类似的题,大家可要注意哦,这可是一道困难的题目哦

题目

给你一个整数数组 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

这道题仔细看一看,品一品,

这个滑动窗口是不是就是一个队列呀;

这个获取滑动窗口中的最大值,是不是就是获取队列中的最大值呀。

那和上面的题目是不是一样呀,😝。

当然这道题里面我们可以只用一个队列,因为上面一道题里面我们不知道shift出去的值是多少,所以要存储在queue里面,这道题我们可以清楚的知道要shift的出去的值,所以我们可以只用duque就可以解出来了

题解

// 这道题就是一个模板题
var maxSlidingWindow = function(nums, k) {
    let result = [];
    // 只需要设置一个双端队列,因为我们知道shift出来的值是多少
    let duque=[];

    for(let i=0;i<nums.length;i++){
        // 什么时候push
        while(duque.length&&nums[i]>duque[duque.length-1]){
            duque.pop();
        }
          
       duque.push(nums[i]);
        // 什么时候pop
        if(i>k-1&&nums[i-k]===duque[0]){
            duque.shift();
        }
        result.push(duque[0]);
    }
    return result.slice(k-1);
};

参考