【力扣】栈和队列

在跟随代码随想录刷题的过程中,完成栈和队列部分的相关题目,所以在此进行一个小总结。

本篇笔记涉及的力扣题目如下:【可以使用Ctrl+F进行搜索~】

  1. 232. 用栈实现队列
  2. 225. 用队列实现栈
  3. 20. 有效的括号
  4. 1047. 删除字符串中的所有相邻重复项
  5. 150. 逆波兰表达式求值
  6. 239. 滑动窗口最大值
  7. 347. 前 K 个高频元素

栈和队列相关问题的一点小总结

  1. 栈和队列能够相互转换,以解决相对应的问题
  2. 栈的定义:Stact< T > stack = new Stack<>();队列的定义:队列分为Queue, Deque, PriorityQueue等,一般情况下能够使用Deque(双端队列)解决问题;Deque< T > deque = new LinkedList<>()
  3. Stack以及Queue的相关操作:Java 实例 - 队列(Queue)用法 Java Stack 类

题目:232. 用栈实现队列

解题思路

  • 仅使用栈的基本操作实现队列,队列:先进先出
  • 新建两个栈用于模拟进队和出队操作
  • 需要注意的是,函数dumpstackIn: 当stackOut不为空时,则出队直接从stackOut操作即可【画图模拟可知】
  • 如进栈1234,出队列的顺序为1234,若取出1,则需要将4321依次取出放入stackOut,再操作stackOut的栈首元素,即1;此时stackOut中元素为234;
  • 入队5,将5放入stackIn中,队首元素出队,即取出元素2,而不用判断刚入栈的元素5

复杂度

  • 时间复杂度:从stackIn将元素取出,放入stackOut需要的时间为: O(n)
  • 空间复杂度: O(n)

Java代码实现

class MyQueue {
    Stack<Integer> stackIn;
    Stack<Integer> stackOut;

    public MyQueue() {
        stackIn = new Stack<>();
        stackOut = new Stack<>();
    }
    
    public void push(int x) {
        stackIn.push(x);
    }
    
    public int pop() {
        dumpstackIn();
        return stackOut.pop();
    }
    
    public int peek() {
        dumpstackIn();
        return stackOut.peek();
    }
    
    public boolean empty() {
        return stackIn.isEmpty() && stackOut.isEmpty();
    }

    // 将栈stackIn中的元素清空,以满足队列的先进先出
    public void dumpstackIn(){
        if(!stackOut.isEmpty())  return;
        while(!stackIn.isEmpty()){
            stackOut.push(stackIn.pop());
        }
    }
}

/**
 * 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();
 */

参考资料:

  1. Java Stack 类
  2. 232. 用栈实现队列

题目:225. 用队列实现栈

解题思路

  • 设置两个队列,元素主要存储在queue1,queue2为辅助队列,即辅助queue1进行排序,以实现栈的后进先出
  • 元素位置的调换主要实现实在元素入栈时,使用交换操作来改变元素的位置

复杂度

  • 时间复杂度: 在push操作时,需要移动队列中的元素,故时间复杂度为O(n)

  • 空间复杂度: O(n)

Java代码实现

class MyStack {
    Queue<Integer> queue1;
    Queue<Integer> queue2; 

    public MyStack() {
        queue1 = new LinkedList<>();
        queue2 = new LinkedList<>();
    }
    
    public void push(int x) {
        queue2.offer(x);
        while(!queue1.isEmpty()){
            queue2.offer(queue1.poll());
        }

        // 交换queue1和queue2,将元素均放入queue1中
        // 放入一个元素,就将元素的顺序调换位置,放入queue1
        Queue<Integer> queueTemp;
        queueTemp = queue1;
        queue1 = queue2;
        queue2 = queueTemp;
    }
    
    public int pop() {
        return queue1.poll();
    }
    
    public int top() {
        return queue1.peek();
    }
    
    public boolean empty() {
        return queue1.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();
 */

参考资料:

  1. 225. 用队列实现栈
  2. Java 实例 - 队列(Queue)用法

题目:20. 有效的括号

解题思路

  • 匹配括号,需要将左右括号按照顺序依次进行匹配
  • 方法1:使用栈进行模拟;方法2:使用双端队列deque时,需要注意获取元素的位置(队头 / 队尾)
  • 当遇到左括号,就将相应的右括号进栈;当遇到右括号,就查看栈顶元素是否和当前的右括号相同即可
  • 右括号匹配失败的两种情况:1.没有相对应的左括号(栈为空);2.左括号不能够进行匹配(栈顶元素不匹配)
  • 最后处理完整个字符串,如果栈不为空,则返回false
  • 特殊情况:当给定字符串长度为奇数时,永远不能够进行匹配

Java代码实现 -- stack

class Solution {
    public boolean isValid(String s) {
        if(s.length() % 2 == 1) {
            return false;
        }

        Stack<Character> stack = new Stack<>();
        for(int i=0; i<s.length(); i++) {
            if(s.charAt(i) == '(') {
                stack.push(')');
            } else if(s.charAt(i) == '[') {
                stack.push(']');
            } else if(s.charAt(i) == '{') {
                stack.push('}');
            } else if(stack.isEmpty() || stack.pop() != s.charAt(i)) {
                // 执行到此,说明遇到了右括号;匹配失败的两种情况:1.没有相对应的左括号(栈为空);2.左括号不能够进行匹配(栈顶元素不匹配)
                return false;
            }
            //  else {
            //     stack.pop();
            // }
        }
        
        return stack.isEmpty();
    }
}

Java代码实现 -- deque

class Solution {
    public boolean isValid(String s) {
        if(s.length() % 2 == 1) {
            return false;
        }
        Deque<Character> deque = new LinkedList<>();
        
        for(int i=0; i<s.length(); i++) {
            if(s.charAt(i) == '(') {
                deque.offer(')');
            } else if(s.charAt(i) == '[') {
                deque.offer(']');
            } else if(s.charAt(i) == '{') {
                deque.offer('}');
            } else if(deque.isEmpty() || deque.peekLast() != s.charAt(i)) {
                return false;
            } else {
                deque.removeLast();
            }
        }

        return deque.isEmpty();
    }
}

参考资料

  1. 20. 有效的括号
  2. Java.util.ArrayDeque.removeLast() 方法
  3. Java LinkedList

题目:1047. 删除字符串中的所有相邻重复项

解题思路

  • 重复项删除操作:删除两个相邻且相同的字母;例如:"abbaca" -> 删除b,"aaca" -> 删除a,"ca"
  • 将给定字符串中的元素依次进栈,如果该元素与栈顶元素相同,则进行出栈操作
  • 同理,此处也可以使用双端队列(deque)方式
  • 最后使用字符串拼接或者StringBuilder方法,将栈中元素依次出栈,并按倒序进行拼接即可

Java代码实现 -- stack+字符串拼接

class Solution {
    public String removeDuplicates(String s) {
        Stack<Character> stack = new Stack<Character>();
        for(int i=0; i<s.length();i++){
            char ch;
            ch = s.charAt(i);
            if(stack.isEmpty() || ch != stack.peek()){
                // 栈为空或者栈顶元素和ch不相同,则将该元素入栈
                stack.push(ch);
            } else {
                // 否则出栈
                stack.pop();
            }
        }
        String str = "";
        while(!stack.isEmpty()){
            str = stack.pop() + str;
        }
        return str;
    }
}

Java代码实现 -- stack+StringBuilder()

class Solution {
    public String removeDuplicates(String s) {
        Stack<Character> stack = new Stack<>();

        for(int i=0; i<s.length(); i++) {
            if(stack.isEmpty()) {
                stack.push(s.charAt(i));
                continue;
            }

            if(s.charAt(i) != stack.peek()) {
                stack.push(s.charAt(i));
            } else {
                stack.pop();
            }
        }

        StringBuilder sb = new StringBuilder();
        while(!stack.isEmpty()) {
            sb = sb.insert(0, stack.pop());
        }

        return sb.toString();
    }
}

Java代码实现 -- deque

class Solution {
    public String removeDuplicates(String s) {
        Deque<Character> deque = new LinkedList<>();

        for(int i=0; i<s.length(); i++) {
            if(deque.isEmpty()) {
                deque.offer(s.charAt(i));
                continue;
            }

            if(s.charAt(i) != deque.peekLast()) {
                deque.offer(s.charAt(i));
            } else {
                deque.removeLast();
            }
        }

        StringBuilder sb = new StringBuilder();
        while(!deque.isEmpty()) {
            sb = sb.append(deque.pollFirst());
        }

        return sb.toString();
    }
}

参考资料

  1. 1047. 删除字符串中的所有相邻重复项
  2. Java StringBuffer 和 StringBuilder 类

题目:150. 逆波兰表达式求值

解题思路

  • 根据提示可知,该操作适合用栈来解决
    图片.png
  • 当遇到数字时,将数字放入栈中;当遇到符号时,从栈顶弹出两个元素,根据相应的符号进行数值计算;并将计算结果放入栈中,重复以上过程即可。
  • 细节问题:1.优先弹出的操作数为第二个操作数,对于“-”和“/”操作需要特殊处理;2. 注意tokens数组为["2","1","+","3","*"],每一个元素均为"",为String类型,不是char类型;因而在进行比较时应该使用equals方法,而非==;3.String -> int: Integer.parseInt(String);4.int -> String: String.valueOf(int);

复杂度

  • 时间复杂度: 该算法仅遍历了一遍字符串数组,所以时间复杂度为O(n)
  • 空间复杂度: O(n)

Java代码实现

class Solution {
    public int evalRPN(String[] tokens) {
        Stack<Integer> stack = new Stack<Integer>();
        for(int i=0; i<tokens.length; i++){
            if("+".equals(tokens[i]) || "-".equals(tokens[i]) || "*".equals(tokens[i]) || "/".equals(tokens[i])){
                int num1 = stack.pop();
                int num2 = stack.pop();
                if("+".equals(tokens[i])){
                    stack.push(num1 + num2);
                } else if("-".equals(tokens[i])){
                    stack.push(num2 - num1);
                } else if("*".equals(tokens[i])){
                    stack.push(num1 * num2);
                } else {
                    stack.push(num2 / num1);
                }
            } else {
                stack.push(Integer.parseInt(tokens[i]));
            }
        }
        
        return stack.pop();
    }
}

参考资料

  1. 150. 逆波兰表达式求值
  2. JAVA 中 string 和 int 互相转化

题目:239. 滑动窗口最大值

方法一:自定义队列添加元素方法

解题思路

  • queue.add(),在添加元素时,使用queue.getLast()获取队尾元素,若添加元素大于队尾元素,则将队尾元素移除;重复上述过程,使队列中的元素按递减序排列。【当需要取出最大值时,将队首元素取出即可】
  • 对于num[0],num[1],num[2],将三个元素依次取出放入queue中
  • nums[3]及以后元素,依次从nums[]中取出每个元素,放入队列中
  • 操作方法:将nums[i-k]移除队列【此时,需要判断nums[i-k]是否在队列中,在自定义队列操作中实现】,nums[i]进入队列,取出最大元素放入res中。

复杂度

  • 时间复杂度: O(n)
  • 空间复杂度: O(n)

Java代码实现

class MyQueue {
    Deque<Integer> queue = new LinkedList<>();

    public void add(int val){
        // 添加元素时,将比val小的元素移除,使整个队列元素呈递减状态,队首元素为最大元素
        while(!queue.isEmpty() && val > queue.getLast()){
            queue.removeLast();
            // queue.getLast()获取队尾元素,removeLast()移除队尾元素
        }
        queue.add(val);
    }

    public int peek(){
        return queue.peek();
    }

    public void poll(int val){
        // 判断当前元素是否在队列中,即是否在add时已去除
        if(!queue.isEmpty() && val == queue.peek()){
            queue.poll();
        }
    }
    
}

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        MyQueue queue = new MyQueue();
        int len = nums.length - k + 1;
        int[] res = new int[len];
        int count = 0;

        for(int i=0; i<k; i++){
            queue.add(nums[i]);
        }
        res[count++] = queue.peek();

        for(int i = k; i < nums.length; i++){
            queue.poll(nums[i-k]);
            queue.add(nums[i]);
            res[count++] = queue.peek();
        }
        return res;
    }
}

方法二:使用双端队列完成

解题思路

  • 依次遍历整个数组,使用双端队列记录最大元素的下标,通过下标的进栈和出栈,控制队列中的元素个数
  • 保证在[i - k + 1, i] 中选到最大值,需要满足:1. 队头结点的下标应该在[i-k+1, i]范围内,不符合该范围则弹出;2. 保证每次放入的元素需要大于队尾元素;同时满足以上两点,那么将该元素进队即可
  • 何时将元素放入res中?队列中的元素是按照递减序列排序的,如果i增长到k-1时,即滑动窗口移动一下,就将队列的队头元素放入res中即可。

Java代码实现

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        Deque<Integer> deque = new LinkedList<>();
        int n = nums.length;

        // 定义res数组,使用idx控制数组的长度
        int[] res = new int[n-k+1];
        int idx = 0;
        
        for(int i=0; i<n; i++) {
            // 队头结点的下标应该在[i-k+1, i]范围内,不符合该范围则弹出;注意:队列中放置的是nums数组的下标
            while(!deque.isEmpty() && deque.peek()<i-k+1) {
                deque.poll();
            }

            // 保证每次放入的元素需要大于队尾元素
            while(!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
                deque.pollLast();
            }

            deque.offer(i);

            if(i >= k-1) {
                res[idx++] = nums[deque.peek()];
            }
        }
        return res;
    }
}

参考资料

  1. 239. 滑动窗口最大值
  2. Java.util.ArrayDeque.removeLast() 方法

题目:347. 前 K 个高频元素

方法一:大顶堆

解题思路

  • 将nums数组中的元素依次放入map(数值--出现个数)中,之后再依次取出map中的KV属性对,放入queue中,最后将结果写入res中即可
  • 定义优先队列PriorityQueue,PriorityQueue<int[]> pq = new PriorityQueue<>((a,b)->b[1]-a[1]),表示根据两属性对的value值进行排序,即根据出现次数进行排序。此处是将出现次数多的放在前面,因而为构建一个大顶堆
  • 将map中的结果放入queue中,pq.add(new int[]{ entry.getKey(), entry.getValue() })
  • res中仅输出数值,因而仅需要K-V对中的key,即pq.poll()[0]

Java代码实现

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> map = new HashMap<>();
        for(int num : nums){
            map.put(num, map.getOrDefault(num, 0)+1);
        }

        PriorityQueue<int[]> pq = new PriorityQueue<>((a,b)->b[1]-a[1]);
        for(Map.Entry<Integer, Integer> entry : map.entrySet()){
            pq.add(new int[]{
                entry.getKey(), entry.getValue()
            });
        }

        int res[] = new int[k];
        for(int i=0; i<k; i++){
            res[i] = pq.poll()[0];
        }
        return res;
    }
}

方法二:小顶堆

解题思路

  • 大致过程同上,唯一区别在定义PriorityQueue时为小顶堆
  • 定义优先队列PriorityQueue,PriorityQueue<int[]> pq = new PriorityQueue<>((a,b)->a[1]-b[1]),表示根据两属性对的value值进行排序,即根据出现次数进行排序。此处是将出现次数少的放在前面,因而为构建一个小顶堆

Java代码实现

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        int[] res = new int[k];

        // 将nums中的元素依次放入map中
        HashMap<Integer, Integer> map = new HashMap<>();
        for(int i=0; i<nums.length; i++) {
            map.put(nums[i], map.getOrDefault(nums[i], 0)+1);
        }
        // System.out.println(map.get(1));

        // 定义优先队列,使map中的元素依次进队出队,队列中的元素保持为前k个,构建小顶堆(出现次数:小->大)
        PriorityQueue<int[]> pq = new PriorityQueue<>((a,b) -> a[1] - b[1]);
        for(Map.Entry<Integer, Integer> entry : map.entrySet()) {
            if(pq.size()<k){
                pq.add(new int[]{entry.getKey(),entry.getValue()});
            }else{
                if(entry.getValue()>pq.peek()[1]){
                    pq.poll();
                    pq.add(new int[]{entry.getKey(),entry.getValue()});
                }
            }
        }

        for(int i=0; i<k; i++) {
            res[i] = pq.poll()[0];
        }

        return res;
    }
}

参考资料

  1. 347. 前 K 个高频元素
  2. Java Map 接口
  3. 详解JAVA中PriorityQueue的具体使用 | java基础
posted @ 2023-05-31 10:20  是你亦然  阅读(34)  评论(0)    收藏  举报