代码随想录 栈与队列模块小结

这部分的内容相对来说简单一些,只需要记住栈和队列的结构特点就比较好做了。

栈:先进后出

队列:先进先出

栈与队列的题目类型,关键是看选择哪一种数据结构。

一、栈模拟队列

由于栈和队列只是出入的顺序不同,所以相互之间是可以转换的。

使用两个栈来模拟队列,就要保证栈顶的元素一定是最先加入的元素,这样就可以保证栈顶弹出时的元素是模拟队列的队头元素。

232. 用栈实现队列 - 力扣(LeetCode)

代码如下

class MyQueue {
    public Stack<Integer> s1;
    public Stack<Integer> s2;
    public MyQueue() {
        s1 = new Stack();
        s2 = new Stack();
    }
    
    public void push(int x) {
        // 始终保持新加入的元素在栈底,先加入的元素在栈顶
        while (!s1.isEmpty()) s2.push(s1.pop());
        s1.push(x);
        while (!s2.isEmpty())s1.push(s2.pop());
    }
    
    public int pop() {
        return s1.pop();
    }
    
    public int peek() {
        return s1.peek();
    }
    
    public boolean empty() {
        return s1.isEmpty() ? true : false;
    }
}

二、队列模拟栈

队列模拟栈,可以使用两个队列来模拟,也可以直接使用双向队列来模拟。

使用两个队列来模拟,就要保证队头的元素是最后加入的元素,这样可以保证队头弹出的元素是模拟栈的栈顶元素。

225. 用队列实现栈 - 力扣(LeetCode)

代码如下

class MyStack {
    Queue<Integer> que1;
    Queue<Integer> que2;
    public MyStack() {
        que1 = new LinkedList();
        que2 = new LinkedList();
    }
    
    public void push(int x) {
        while (!que1.isEmpty()) que2.add(que1.remove());
        que1.add(x);
        while (!que2.isEmpty()) que1.add(que2.remove());
    }
    
    public int pop() {
        return que1.remove();
    }
    
    public int top() {
        return que1.peek();
    }
    
    public boolean empty() {
        return que1.isEmpty() ? true : false;
    }
}

也可以使用双向队列来实现,逻辑会更简单一些,队尾进队尾出就行。

代码如下

class MyStack {

    private Deque<Integer> queue;

    public MyStack() {
        queue = new ArrayDeque<>();
    }
    
    public void push(int x) {
        queue.addLast(x);
    }
    
    public int pop() {
        return queue.removeLast();
    }
    
    public int top() {
        return queue.getLast();
    }
    
    public boolean empty() {
        return queue.isEmpty();
    }
}

三、栈经典题目

栈的经典题目,那首先肯定是括号匹配了。

20. 有效的括号 - 力扣(LeetCode)

一旦栈顶的符号与遍历的符号匹配上了,那么就弹出栈顶元素,否则就将该符号添加入栈中。

最后看栈是否为空就可以了。

代码如下

class Solution {
    public boolean isValid(String s) {

        Stack<Character> st = new Stack();
        char[] cs = s.toCharArray();
        for (char c : cs) {
            if (!st.isEmpty()) {
                if (st.peek() == '(' && c == ')') {
                    st.pop();
                    continue;
                }
                else if (st.peek() == '[' && c == ']') {
                    st.pop();
                    continue;
                }
                else if (st.peek() == '{' && c == '}') {
                    st.pop();
                    continue;
                }
            }
            st.push(c);
        }
        return st.isEmpty();
    }
}

以及逆波兰表达式

150. 逆波兰表达式求值 - 力扣(LeetCode)

逆波兰表达式是后缀表达式,与 x + y这种中缀表达式不同,后缀表达式更有利于计算机计算数据。

由于题目说了,表达式一定是有效的,所以不用考虑数字或者运算符的多和少情况。

一旦遇到运算符,那么就把栈顶的两个元素都取出来做运算,运算完后将结果再放回去栈中就可以了;如果不是运算符,那么就直接添加入栈中。

代码如下

class Solution {
    public int evalRPN(String[] tokens) {
        Stack<Integer> stack = new Stack();
        for (String s : tokens) {
            String res = s;
            if (!stack.isEmpty() && s.equals("+") || s.equals("-") || s.equals("*") || s.equals("/")) {
                int second = stack.pop();
                int first = stack.pop();
                if ("+".equals(s)) {
                    res = String.valueOf(first + second);
                }else if ("-".equals(s)) {
                    res = String.valueOf(first - second);
                }else if ("*".equals(s)) {
                    res = String.valueOf(first * second);
                }else if ("/".equals(s)){
                    res = String.valueOf(first / second);
                }
            }
            stack.push(Integer.valueOf(res));
        }
        return stack.pop();
    }
}

四、单调队列

单调队列的特点就是队列中的元素是有序的

以LC_239滑动窗口最大值为例。

239. 滑动窗口最大值 - 力扣(LeetCode)

这道题是一道困难题,每次都要选择出窗口中的最大值。

那么很容易想到,假如我们维护一个数据结构,它每次弹出的元素都是窗口最大值,那么就好解决了。

因为遍历元素时的滑动窗口是头进尾出,即先进先出的结构,符合队列的特点,那么就要使用队列来作为基础数据结构来实现。

要保证队头的元素一定是窗口最大值,就要对进出队列的元素进行处理。

  • 如果说即将进入窗口的元素大于队尾的元素,那么就要一直将队尾元素弹出,直到队列为空,或者当前元素小于等于队尾元素,才将该元素加入队列。
  • 如果说窗口要移除的元素等于队头元素,才将队头元素弹出,否则不作任何操作。

这样就可以保证,队列是一个有序队列,且是单调递减队列,队头元素一定是最大的元素。

窗口要移除的元素是不能出现在队列里的,因此当窗口要移除元素与队头元素相等,就要将队头元素弹出。

这样我们的数据结构就搭建好了。

由于需要将队尾元素弹出,所以要使用双向队列来实现。

// 单调队列
public class MyQueue {
    public Deque<Integer> que;
    public MyQueue() {
        que = new ArrayDeque();
    }

    // 队头的一定是最大的元素
    public int peek() {
        if (!que.isEmpty()) return que.peekFirst();
        return -1;
    }

    public void remove(int x) {
        // 队头的元素等于窗口即将要移除的元素 才移除
        if (!que.isEmpty() && this.peek() == x) que.removeFirst();
    }

    public void add(int x) {
        // 要加入窗口的元素如果大于队尾元素 则一直删除 直到为空或者小于等于为止
        while (!que.isEmpty() && x > que.peekLast()) {
            que.removeLast();
        }
        que.addLast(x);
    }
}

那么之后就是遍历数组中的元素了,一旦遍历数组的长度等于窗口大小k,之后就要执行添加、移除、和获取最大值的操作。

代码如下

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        MyQueue queue = new MyQueue();
        int[] res = new int[nums.length - k + 1];
        // 先把前k - 1个加进去 注意不一定全加进去
        for (int i = 0; i < k - 1; i++) {
            queue.add(nums[i]);
        }
        for (int i = 0; i < res.length; i++) {
            queue.add(nums[i + k - 1]);  // 第一个窗口的最后一个元素
            res[i] = queue.peek();      // 返回队头元素 一定是最大的
            queue.remove(nums[i]);      // 移除队头 注意有可能不变
        }
        return res;
    }
}

五、优先队列

优先队列顾名思义,队列中的元素是有优先级的,优先级的不同会影响元素在队列中的位置。

以LC_347为例。

347. 前 K 个高频元素 - 力扣(LeetCode)

要求前k大的频率的数字。

说到频率,首先想到是用map来保存每一个数字的频率,因为要保存的数值为2个,<数,频率>。

那么要前k大频率,如果让频率大的entry(map中的<k,v>对)排在队列前面,那么要前k大的,直接取前k个就行了。

1、用map保存频率:

        // 使用哈希表保存频率
        HashMap<Integer, Integer> map = new HashMap();
        for (int n : nums) {
            map.put(n, map.getOrDefault(n, 0) + 1);
        }

2、创建优先级队列,并指定优先级的规则,entry中的value大的优先级高,排在前面。

        // 使用优先队列对表进行排序
        // entry是map中的每一个<k,v>对
        Set<Map.Entry<Integer, Integer>> entrySet = map.entrySet();
        // 参数为排序规则 要new PriorityQueue<>, new PriorityQueue是会报错的
        // (o1, o2) -> o2.getValue() - o1.getValue()为大顶堆,降序排列 
        PriorityQueue<Map.Entry<Integer, Integer>> que = new PriorityQueue<>((o1, o2) -> o2.getValue() - o1.getValue());
        // 将entry放入队列中
        for (Map.Entry<Integer, Integer> e : entrySet) {
            que.add(e);
        }

3、取频率前k大的数字

        int[] res = new int[k];
        for (int i = 0; i < res.length; i++) {
            res[i] = que.remove().getKey();
        }
        return res;

完整代码如下

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        // 使用哈希表保存频率
        HashMap<Integer, Integer> map = new HashMap();
        for (int n : nums) {
            map.put(n, map.getOrDefault(n, 0) + 1);
        }

        // 使用优先队列对表进行排序
        Set<Map.Entry<Integer, Integer>> entrySet = map.entrySet();
        // 参数为排序规则 要new PriorityQueue<>, new PriorityQueue是会报错的
        // (o1, o2) -> o2.getValue() - o1.getValue()为大顶堆,降序排列
        PriorityQueue<Map.Entry<Integer, Integer>> que = new PriorityQueue<>((o1, o2) -> o2.getValue() - o1.getValue());
        // 将entry放入队列中
        for (Map.Entry<Integer, Integer> e : entrySet) {
            que.add(e);
        }
        int[] res = new int[k];
        for (int i = 0; i < res.length; i++) {
            res[i] = que.remove().getKey();
        }
        return res;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值