数据结构算法刷题--栈与队列

1、栈与队列理论基础

  • 栈:先进后出
  • 队列:先进先出

2、用栈实现队列

  • 题目:https://leetcode.cn/problems/implement-queue-using-stacks/
  • 思路:用两个栈,一个栈作压入,一个栈作弹出;
    • 压入正常压;
    • 弹出的时候如果弹出栈里面为空,就按把压入栈的所有元素全部压入弹出栈,这样先被压入的元素就仍然会先弹出;
    • 获取队头元素就复用弹出,将弹出(该操作可以复用)栈栈顶元素弹出再压入回来;
  • 代码实现:
class MyQueue {
    public Stack<Integer> stackIn;
    public Stack<Integer> stackOut; 

    public MyQueue() {
        stackIn = new Stack<>();
        stackOut = new Stack<>();
    }
    
    public void push(int x) {
        stackIn.push(x);
    }
    
    public int pop() {
        if(stackOut.isEmpty()){
            // 输出栈如果为空,直接将输入栈的数据全部倒进来
            while(!stackIn.isEmpty()){
                stackOut.push(stackIn.pop());
            }
        }
        // 正常弹出
        return stackOut.pop();
    }
    
    public int peek() {
        // 复用 pop
        int val = this.pop();
        // 将弹出的值再压回来
        stackOut.push(val);

        return val;
    }
    
    public boolean empty() {
        return stackIn.isEmpty() && stackOut.isEmpty();
    }
}

/**
 * Your MyQueue object will be instantiated and called as such:
 * MyQueue obj = new MyQueue();
 * obj.push(x);
 * int param_2 = obj.pop();
 * int param_3 = obj.peek();
 * boolean param_4 = obj.empty();
 */

3、用队列实现栈

  • 题目:https://leetcode.cn/problems/implement-stack-using-queues/
  • 思路:只用一个队列实现,需要注意的地方就是每次插入新元素后,为了让其成为对头,在插入后将其前面的所有元素从队列弹出再插入;
    • 插入时在当前元素被插入以后,将其前面的所有元素弹出再插入一遍,实现栈的后入先出;
    • 弹出正常弹出;
    • 栈顶元素直接获取队列的队头元素;
    • 判断是否为空调用队列的判断为空;
  • 代码实现:
class MyStack {
    // 用队列模拟栈:每插入一个元素将队列里面本来的元素全部重新弹出添加进队列就好了

    Queue<Integer> queue;

    public MyStack() {
        queue = new LinkedList<>();
    }
    
    public void push(int x) {
        queue.offer(x);
        int size = queue.size();
        while(size-- > 1){
            queue.offer(queue.poll());
        }
    }
    
    public int pop() {
        return queue.poll();
    }
    
    public int top() {
        return queue.peek();
    }
    
    public boolean empty() {
        return queue.isEmpty();
    }
}

/**
 * Your MyStack object will be instantiated and called as such:
 * MyStack obj = new MyStack();
 * obj.push(x);
 * int param_2 = obj.pop();
 * int param_3 = obj.top();
 * boolean param_4 = obj.empty();
 */

4、有效的括号

  • 题目:https://leetcode.cn/problems/valid-parentheses/
  • 思路:就近匹配,利用栈,每当出现一个右括号,栈顶应该有一个与其对应的;
    • 每遇到一个左括号,将其对应的右括号(方便判断)压入栈;
    • 每遇到一个右括号,如果栈不为空并且栈顶与其相同——跳过下一个;
    • 否则,返回false;
  • 代码实现:
class Solution {
    // 当遇到左括号,就把对应的右括号放进栈里(根据栈的特性就可以实现就近的匹配);
    // 当遇到右括号,看栈里弹出的元素是否直接匹配上,匹配不上直接false;匹配上弹出;
    // 最后如果遍历完栈为空了,true

    public boolean isValid(String s) {
        Stack<Character> stack = new Stack<>();

        char c;
        for(int i = 0; i < s.length(); ++i){
            c = s.charAt(i);
            if(c == '('){
                stack.push(')');
            }else if(c == '['){
                stack.push(']');
            }else if(c == '{'){
                stack.push('}');
            }else if(stack.isEmpty() || stack.pop() != c ){
                return false;
            }
        }

        if(stack.isEmpty()){
            return true;
        }

        return false;
    }
}

5、删除字符串中的所有相邻重复项

  • 题目:https://leetcode.cn/problems/remove-all-adjacent-duplicates-in-string/
  • 思路:利用栈的后进先出,每当当前元素与栈顶元素匹配就进行删除操作;为了方便结果是字符串,用字符串StringBuilder 模拟栈;
    • 每遇到一个字符,如果栈(StringBuilder )中有元素并且栈顶元素与其相同,将栈中元素删除;
    • 否则,将其加入栈中;
  • 代码实现:
class Solution {
    // 栈--用字符串来模拟

    public String removeDuplicates(String s) {
        // 字符串模拟栈
        StringBuilder sb = new StringBuilder();
        // 栈顶元素的索引
        int top = -1;

        // 遍历字符串,删除相邻重复项
        char c;
        for(int i = 0; i < s.length(); ++i){
            c = s.charAt(i);
            if(top >= 0 && sb.charAt(top) == c){
                // 栈中有元素,并且栈顶元素与当前字符相同--删除
                sb.deleteCharAt(top--);
            }else{
                // 入栈
                sb.append(c);
                ++top;
            }
        }

        return sb.toString();
    }
}

6、逆波兰表达式求值

  • 题目:https://leetcode.cn/problems/evaluate-reverse-polish-notation/
  • 思路:计算机进行运算的思考方式;栈,遇到数字压入栈中,遇到运算符从栈中取数进行操作;避免了传统运算方式中顺序读取遇到优先级更高的运算符打乱运算顺序;
    • 遇到数字——入栈;
    • 遇到运算符,根据运算符从栈中弹出两个数字进行运算;
    • 总结果最终返回;
  • 代码实现:
class Solution {
    // 栈--计算机运算思想

    public int evalRPN(String[] tokens) {
        Stack<Integer> stack = new Stack<>();

        // 遍历
        for(String s : tokens){
            // System.out.println(s);
            // 如果是运算符--取元素运算
            if("+".equals(s)){
                stack.push(stack.pop() + stack.pop());
            }else if("-".equals(s)){
                int num1 = stack.pop();
                int num2 = stack.pop();
                stack.push(num2 - num1);
            }else if("*".equals(s)){
                stack.push(stack.pop() * stack.pop());
            }else if("/".equals(s)){
                int num1 = stack.pop();
                int num2 = stack.pop();
                stack.push(num2 / num1);
            }else{
                // 是数字--入栈
                stack.push(Integer.parseInt(s));
            }
        }

        return stack.pop();
    }
}

7、滑动窗口最大值

  • 题目:https://leetcode.cn/problems/sliding-window-maximum/
  • 思路:单调队列!滑动窗口滑动时,保持窗口内的数被队列单调记录,只用返回队列头部的元素就可以;
    • 首先要自己实现单调队列,关键在于每次的插入操作执行后能够维持队列内的单调性——每插入一个元素,从队列尾部开始比较,只要尾部元素比它小就弹出并且一直弹到要插入的元素前面的元素都比它大,弹出去的那些元素本来就对统计窗口内的最大值没有意义,当前窗口已经有比他们大的 元素;
    • 弹出元素时因为永远是最大的元素在队头,所以只要窗口移动后要弹出的元素刚好是队列头部的元素才去进行弹出,否则要弹出的小元素放在队列里面也不会影响窗口内的最大值;
  • 代码实现:
class MyQueue {
    private Deque<Integer> deque;

    MyQueue(){
        deque = new LinkedList<>();
    }

    // 向队列插入元素,保持队列内元素单调
    public void push(int val){
        while(!deque.isEmpty() && val > deque.getLast()){
            // System.out.println("弹出:" + deque.pollLast());
            deque.pollLast();
        }
        // System.out.println("插入:" + val);
        deque.offerLast(val);
    }

    // 弹出元素
    public void poll(){
        // System.out.println("弹出队首元素:"+ deque.poll());
        deque.poll();        
    }

    // 获取队首元素
    public int peek(){
        // System.out.println("本轮队首元素:" + deque.peekFirst());
        return deque.peekFirst();
    }
}


class Solution {
    // 单调队列
    public int[] maxSlidingWindow(int[] nums, int k) {
        // 1、初始化数据
        int n = nums.length;
        int len = n - k + 1;

        int[] res = new int[len];

        // 2、初始化自定义队列
        MyQueue queue = new MyQueue();
        for(int i = 0; i < k; ++i){
            queue.push(nums[i]);
        }
        res[0] = queue.peek();

        // 3、窗口滑动
        for(int i = k; i < n; ++i){
            // 此时要剔除一个元素,进来一个元素
            // 如果要删除的元素恰是队首元素才删除,不然直接不用管
            if(nums[i - k] == queue.peek()){
                queue.poll();
            }
            queue.push(nums[i]);
            res[i - k + 1] = queue.peek();
        }

        return res;
    }
}

8、前K个高频元素

  • 题目:https://leetcode.cn/problems/top-k-frequent-elements/
  • 思路:优先级队列!创建的优先级队列设置内部排序方式,大顶堆是栈顶(队首)元素大,最小的在栈底(队尾);小顶堆是栈顶(队首)元素最小,最大的在栈底(队尾);这里利用大顶堆,将所有元素插入优先级队列中,然后弹出前k个元素;
  • 代码实现:
class Solution {
    // 优先级队列--大顶堆

    public int[] topKFrequent(int[] nums, int k) {
        // 1、利用哈希表统计所有元素出现的频率
        Map<Integer, Integer> map = new HashMap<>();
        for(int num : nums){
            map.put(num, map.getOrDefault(num, 0) + 1);
        }

        // 2、将所有元素和其出现频率放入大顶堆,利用出现队列排序
        // 优先队列,大顶堆
        PriorityQueue<int[]> pq = new PriorityQueue<>((int1, int2) -> int2[1] - int1[1]);
        for(Map.Entry<Integer, Integer> entry : map.entrySet()){
            pq.add(new int[]{entry.getKey(), entry.getValue()});
        }

        // 3、返回结果
        int[] res = new int[k];
        for(int i = 0; i < k; ++i){
            res[i] = pq.poll()[0];
        }
        return res;
    }
}

9、栈与队列总结

  • 栈是容器适配器,底层容器使用不同的容器,导致栈内数据在内存中是不是连续分布;
  • 栈经典题目:括号匹配,字符串去重,逆波兰表达式,系统中的目录切换;递归实现借助栈在递归返回的时候重新取回局部变量、 参数值和返回地址;
  • 队列经典题目:
    • 单调队列:单调队列不是直接等价于对窗口里的数据进行排序(否则就是优先队列了),在滑动窗口最大值问题中,单调队列不是一直包含窗口里面的元素,每次插入元素的时候窗口里面一些小的元素可能直接被弹出并不在队列里面,而窗口移动走了的元素也有可能会在队列里面留着;队列最关心的任务是保持窗口的最大值在队首。
    • 优先队列:大顶堆、小顶堆。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值