分类刷算法题(二)栈和队列 | 前端er

853 阅读4分钟
  • 分类刷算法题
  • 这一篇记录的是栈和队列
  • 关于栈和队列的基础知识就不介绍啦,相信都接触过了,直接开刷!

一、栈和队列

  • 20. 有效的括号 - 力扣(LeetCode)
  • 难度: 简单
  • 这个题很经典哈哈哈,记得数据结构教材上也有这道经典题
  • 括号要成立---意味着对称---跟栈的出栈入栈特性一样--比如入栈是12345那么出栈就是54321,与括号是一样的
  • 所以我们只需要在遇到右括号时,与栈顶元素进行比较即可
var isValid = function(s) {
    //奇数一定不符合
    if(s.length % 2  === 1) {
        return false
    }
    //定义一个map来维护括号的关系
    const pairs = new Map([
        [')','('],
        [']','['],
        ['}','{']
    ]);
    //初始化一个栈
    const stk = [];
    //遍历循环s,如果是左括号直接入栈,右括号则需要判断
    for(let ch of s ){
        //判断是否为右括号
        if(pairs.has(ch)){
            //是右括号则判断该右括号是否能和栈顶元素匹配
            if(stk.length ==0 || stk[stk.length -1] != pairs.get(ch)){
                //如果该栈为空,或者栈顶元素不匹配直接返回FALSE
                return false
            }
            //没有问题的话弹出栈顶元素
            stk.pop();
        }else{
            stk.push(ch);
        }
    }
    if(stk.length !=0 ) return false
    return true
};
  • 739. 每日温度 - 力扣(LeetCode)
  • 难度:中等
  • 这道题的思路是去维持一个递减栈,只要出现了一个数字,它打破了这种单调递减的趋势,也就是说它比前一个温度值高,这时我们就对前后两个温度的索引下标求差,得出前一个温度距离第一次升温的目标差值
  • 思路:
    • 初始化存放索引的递减栈
    • 初始化结果数组,占位为0
    • 进入for循环
      • 当存在打破递减趋势的温度值,进入while循环:
        • 取出栈顶值
        • 对前后两个温度的索引下标求差
      • 将索引推入栈里
var dailyTemperatures = function(temperatures) {
    const len = temperatures.length;
    //初始化存放索引的递减栈
    const stack = [];
    //初始化结果数组,占位为0
    const res = (new Array(len)).fill(0);
    for(let i = 0; i< len; i++) {
        //出现了一个数字,它打破了这种单调递减的趋势
        while(stack.length && temperatures[i] > temperatures[stack[stack.length-1]]){
            //取出栈顶值
            const top = stack.pop();
            res[top] = i - top;
        }
        stack.push(i);
    }
    return res
};
  • 剑指 Offer 09. 用两个栈实现队列 - 力扣(LeetCode)
  • 232. 用栈实现队列 - 力扣(LeetCode)
  • 难度:简单
  • 这两道题都是使用两个栈去实现一个队列,做这道题之前首先要搞清楚栈与队列的区别
  • 主要在栈1实现推入操作,主要在栈2实现推出操作
  • 思路:
    • 初始化stack1,stack2
    • push: 直接使用push推入stack1
    • pop
      • 如果stack2不为空,那么直接推出栈顶数据
      • 如果为空,则将stack1的元素pop出来之后push进stack2(这样stack栈底的元素就到了stack的栈顶)
    • peek:与pop操作思路一样,在return的时候不执行pop操作即可
    • empty:只要stack1和stack2都为空,那么队列即为空
var MyQueue = function() {
    this.stack1 = [];
    this.stack2 = [];
};
MyQueue.prototype.pop = function() {
    //如果stack2为空的话就考虑从stack1移过来
    if(this.stack2.length == 0) {
        while(this.stack1.length!=0) {
        this.stack2.push(this.stack1.pop());
        }
    }
    //不为空时直接pop出栈顶值
    return this.stack2.pop();
};
MyQueue.prototype.peek = function() {
     //如果stack2为空的话就考虑从stack1移过来
    if(this.stack2.length == 0) {
        while(this.stack1.length!=0) {
        this.stack2.push(this.stack1.pop());
        }
    }
    //不为空时直接pop出栈顶值
    return this.stack2[this.stack2.length-1];
};

MyQueue.prototype.empty = function() {
    if(this.stack1.length == 0 && this.stack2.length == 0) return true
    return false
};
  • 155. 最小栈 - 力扣(LeetCode)
  • 难度: 中等
  • 一开始看到这道题,求最小值,只需要初始化一个INfinity,然后遍历一次就行,so easy,时间复杂度是O(n)也还行,咔咔地就往下写
var MinStack = function() {
    this.stack = [];
};
MinStack.prototype.push = function(val) {
    this.stack.push(val);
};
MinStack.prototype.pop = function() {
    return this.stack.pop();
};
MinStack.prototype.top = function() {
    return this.stack[this.stack.length-1];
};
MinStack.prototype.getMin = function() {
    let min = Infinity;
    for(let i of this.stack) {
        if(i < min) {
            min = i;
        }
    }
    return min
};
  • 结果一看执行用时只打败了百分之7.2,肯定不对劲,肯定还能再进一步优化,于是就联想起了上一道用的两个栈解决问题,用空间去换时间

image.png

  • 思路:用一个辅助栈来维持一个递减栈(看着是不是很熟悉,在每日温度那一题也出现过哦),这样的话我每次需要最小值的时候只需要从栈顶pop出来即可
var MinStack = function() {
    this.stack1 = [];
    this.stack2 = [];
};
MinStack.prototype.push = function(val) {
    this.stack1.push(val);
    //当辅助栈为空,或者要推进入栈的值比辅助栈栈顶元素小或者等于的时候,就将该值也推进去辅助栈
    //那么这样辅助栈的栈顶元素一定就是目前的最小值
    if(this.stack2.length == 0 || val <= this.stack2[this.stack2.length-1]){
        this.stack2.push(val);
    }
};
MinStack.prototype.pop = function() {
    //值推出去的时候要判断是否与辅助栈顶元素相同,相同的话也要从辅助栈顶推出去,使最小值保持是一个有效值
    if(this.stack1[this.stack1.length-1] == this.stack2[this.stack2.length-1]) {
        this.stack2.pop();
    }
    return this.stack1.pop();
};
MinStack.prototype.top = function() {
    return this.stack1[this.stack1.length-1];
};
//经过辅助栈的处理后,这里就不需要再去遍历一次啦,只需要返回辅助栈的栈顶元素,那么时间复杂度就变成了O(1)
MinStack.prototype.getMin = function() {
    return this.stack2[this.stack2.length-1]
};

image.png

  • 剑指 Offer 31. 栈的压入、弹出序列 - 力扣(LeetCode)
  • 难度:中等
  • 这道题也是用了上述用过的方法辅助栈:借用一个辅助栈 ,模拟 压入 / 弹出操作的排列。根据是否模拟成功,即可得到结果
  • 入栈操作: 按照压栈序列的顺序执行。
  • 出栈操作: 每次入栈后,循环判断 “栈顶元素 == 弹出序列的当前元素” 是否成立,将符合弹出序列顺序的栈顶元素全部弹出
var validateStackSequences = function(pushed, popped) {
    //初始化栈
    let stack = [];
    for(let i = 0;i<pushed.length;i++) {
        //入栈只需要按照压栈的顺序执行
        stack.push(pushed[i]);
        //如果栈不为空,且栈顶元素为弹出序列的当前元素,则弹出栈元素,同时将弹出序列第一个元素弹出(因为我要比较的是poped[0])
        while(stack.length && stack[stack.length-1] == popped[0]) {
            stack.pop();
            popped.shift();
        }
    }
    //如果栈为空则返回true,否则返回false
    return !stack.length
};

二、 双端队列

  • 239. 滑动窗口最大值 - 力扣(LeetCode)
  • 难度:困难
  • 这道题可以回看我们上一篇链表使用到的双指针法,分别让leftright指向窗口的最大值和最小值,然后每一次滑动窗口就对窗口内的值进行比较,但是写的过程你就能感受到这种做法很 “笨”,如果窗口比较大的话,那么就进行了太多重复的比较,因此很容易超时
var maxSlidingWindow = function(nums, k) {
    //双指针遍历法
    let res = [];
    for(let i = 0;i<nums.length-k+1;i++) {
        let left = i;
        let right = i + k-1;
        let resMax = -Infinity;
        while(left<=right) {
            if(nums[left] > resMax) {
                resMax = nums[left]
            }
            left++;
        }
        res.push(resMax);
    }
    return res
};
  • 这个时候就要推出我们双端队列的做法啦,因为窗口每次只移动一位,那么就说明窗口内的元素只有两个会发送变化,那么在窗口发生移动时,只根据发生变化的元素对最大值进行更新无疑能够让其用时减少很多
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);
    // 当队头元素的索引已经被排除在滑动窗口之外时,将队头元素索引出队
    while (deque.length && deque[0] <= i - k) {
      deque.shift();
    }
    // 判断滑动窗口的状态,只有在被遍历的元素个数大于 k 的时候,才更新结果数组
    if (i >= k - 1) {
      res.push(nums[deque[0]]);
    }
  }
  return res;
};

今天的就刷到这里啦,不够的还可以看看其他分类刷题集哦