(算法)栈与队列(下)

87 阅读2分钟

用栈实现一个队列

使用栈实现队列的下列操作: push(x) -- 将一个元素放入队列的尾部。 pop() -- 从队列首部移除元素。 peek() -- 返回队列首部的元素。 empty() -- 返回队列是否为空。

示例:

MyQueue queue = new MyQueue();
queue.push(1);
queue.push(2);
queue.peek(); // 返回 1
queue.pop(); // 返回 1
queue.empty(); // 返回 false

说明:
你只能使用标准的栈操作 -- 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。
你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
假设所有操作都是有效的 (例如,一个空的队列不会调用 pop 或者 peek 操作)。

题解:

/**
 * 初始化构造函数
 */
const MyQueue = function(){
    this.stack1 = [];
    this.stack2 = [];
}

/**
* @param {number} x
* @return {void}
*/
MyQueue.prototype.push = function(x){
    this.stack1.push(x)
}

/**
* @return {number}
*/
MyQueue.prototype.pop = function(){
    if(!this.stack2.length){
       while(this.stack1.length){
            this.stack2.push(this.stack1.pop())
        }
    }
    return this.stack2.pop()
}

/**
* @return {number}
*/
MyQueue.prototype.peek = function(){
    if(!this.stack2.length){
       while(this.stack1.length){
            this.stack2.push(this.stack1.pop());
        }
    }
    //取栈2的栈顶元素
    return this.stack2[this.stack2.length - 1];
}

/**
* @return {boolean}
*/
MyQueue.prototype.empty = function(){
    return !this.stack1.length && !this.stack2.length;
}

规则:

栈:先进后出;队:先进先出。让栈底的元素首先被取出。

双端队列-滑动窗口问题

给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。

示例:

输入: 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
1 [3 -1 -3] 5 3 6 7
1 3 [-1 -3 5] 3 6 7
1 3 -1 [-3 5 3] 6 7
1 3 -1 -3 [5 3 6] 7
1 3 -1 -3 5 [3 6 7]
最大值分别对应:3 3 5 5 6 7

你可以假设 k 总是有效的,在输入数组不为空的情况下,1 ≤ k ≤ 输入数组的大小。

题解1:双指针 + 遍历

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number[]}
 */
const maxSlidingWindow = function(nums, k){
    const res = [];
    let left = 0;
    let right = k - 1;
    while(right < nums.length){
        //计算最大值
        const max = compMax(nums, left, right);
        res.push(max);
        left++;
        right++;
    }

    return res;
}

/**
 * @param {number[]} nums
 * @param {number} left
 * @param {number} right
 * @return {number}
 */
const compMax = function(nums, left, right){
    if(!nums.length) return;
    let maxNum = nums[left];
    for(let i = left; i <= right; i++){
        if(nums[i] > maxNum) maxNum = nums[i];
    }
    return maxNum;
}

时间复杂度O(kn)

题解2:双端队列

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number[]}
 */
const maxSlidingWindow = function(nums, k){
    const res = [];
    const deque = [];
    for(let i = 0; i < nums.length; i++){
        //小于队尾的值入队是防止队头的值去掉后,剩下的数值中最大值丢失问题(递减)
        //遇到比队尾大的值,把小的值末尾出队
        //并把大的值入队保证队头的值去掉后仍是后面数中的最大值
        while(deque.length && nums[deque[deque.length - 1]] < nums[i]){
            deque.pop()
        }
        //保存索引,为了准确去掉滑动窗口已滑过的值
        deque.push(i);
        
        //小于i-k 即滑动窗口滑过的索引
        while(deque.length && deque[0] <= i - k){
            //队头出队
            deque.shift();
        }
        // 窗口满才会出现最大值
        if(i >= k - 1){
            //放入结果集
            res.push(nums[deque[0]])
        }
    }
    
    return res;
}

时间复杂度O(n), 在窗口发生移动时,只根据发生变化的元素对最大值进行更新,有效的递减队列。

规则:

双端队列就是允许在队列的两端进行插入和删除的队列。既允许使用 pop、push 同时又允许使用 shift、unshift 的数组。