算法-栈和队列

1. 用栈实现队列(LeetCode 232)

题目:请你仅使用两个栈实现队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):
实现 MyQueue 类:

  • void push(int x) 将元素 x 推到队列的末尾
  • int pop() 从队列的开头移除并返回元素
  • int peek() 返回队列开头的元素
  • boolean empty() 如果队列为空,返回 true ;否则,返回 false

思路

  • 用stackin和stackout两个栈模拟队列
  • 在dumpstackin时,需要先确认stackout是否为空;只有stackout为空时才能将stackin中的内容压入

java

  • Stack常用函数:push(), pop(), empty(), peek()(返回栈顶元素,但不删除)
class MyQueue {
    // 用两个栈模拟队列
    Stack<Integer> stackin;
    Stack<Integer> stackout;

    public MyQueue() {
        stackin = new Stack<>();
        stackout = new Stack<>();
    }
    
    public void push(int x) {
        stackin.push(x);
    }
    
    // 将stackin中的元素全部放到stackout中
    private void dumpstackin(){
        //stackout清空后才能继续压入,否则顺序会出现问题
        if(!stackout.empty())   return;
        while (!stackin.empty()){
            stackout.push(stackin.pop());
        }
    }

    //移除并返回队列开头元素
    public int pop() {
        dumpstackin();
        return stackout.pop();
    }
    
    //返回队列开头元素
    public int peek() {
        dumpstackin();
        return stackout.peek();
    }
    
    public boolean empty() {
        return stackin.empty() && stackout.empty();
    }
}

2. 用队列模拟栈(LeetCode 225)

题目:请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。
实现 MyStack 类:

  • void push(int x) 将元素 x 压入栈顶。
  • int pop() 移除并返回栈顶元素。
  • int top() 返回栈顶元素。
  • boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。

思路:唯一需要处理的就是pop()peek()。把que1弹出的只剩最后一个元素,即为栈顶元素。

java

  • 单向列表接口Queue:实现类LinkedList
  • 双向列表接口Deque:实现类ArrayDeque
  • Deque可以使用addLast(), pollFirst, peekFirst()可以等效单向队列的add(), poll(), peek()
class MyStack {

    Deque<Integer> que1 = new ArrayDeque<>();
    Deque<Integer> que2 = new ArrayDeque<>();

    public MyStack() {
    }
    
    public void push(int x) {
        que1.addLast(x);
    }
    
    public int pop() {
        while(que1.size() > 1){
            que2.addLast(que1.pollFirst());
        }
        int res = que1.pollFirst();
        que1 = que2;
        que2 = new ArrayDeque<>();
        return res;
    }
    
    public int top() {
        while(que1.size() > 1){
            que2.addLast(que1.pollFirst());
        }
        int res = que1.pollFirst();
        que2.addLast(res);
        que1 = que2;
        que2 = new ArrayDeque<>();
        return res;
    }
    
    public boolean empty() {
        return (que1.size() == 0);
    }
}

3. 有效的括号(LeetCode 20)

题目:给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。

思路:用栈实现括号匹配

class Solution {
    public boolean isValid(String s) {
        Stack<Character> stack = new Stack<>();
        for(int i = 0; i<s.length(); ++i){
            char ch = s.charAt(i);
            if(ch == '(' || ch == '[' || ch == '{')
                stack.push(ch);
            else {
                if(ch == ')' && !stack.empty() && stack.peek() == '(')
                    stack.pop();
                else if(ch == ']' && !stack.empty() && stack.peek() == '[')
                    stack.pop();
                else if(ch == '}' && !stack.empty() && stack.peek() == '{')
                    stack.pop();
                else return false;
            }
        }
        return stack.empty();
    }
}

4. 删除字符串中的所有相邻重复项(LeetCode 1047)

题目:给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
在 S 上反复执行重复项删除操作,直到无法继续删除。
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
思路:本题和括号匹配类似。最后需要将栈中的内容输出,构建一个新的字符串。
java: stack.size()获取栈的大小

class Solution {
    public String removeDuplicates(String s) {
        Stack<Character> stack = new Stack<>();
        for(int i = 0; i<s.length(); ++i){
            char ch = s.charAt(i);
            if(!stack.empty() && ch == stack.peek())
                stack.pop();
            else 
                stack.push(ch);
        }

        int stack_size = stack.size(); 
        char[] chars = new char[stack_size];
        for(int i = stack_size - 1; i >= 0; --i){
            chars[i] = stack.pop();
        }
        return new String(chars);
    }
}

5. 逆波兰表达式求值(LeetCode 150)

题目:给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。
请你计算该表达式。返回一个表示表达式值的整数。
注意:

  • 有效的算符为 '+'、'-'、'*' 和 '/' 。
  • 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
  • 两个整数之间的除法总是 向零截断 。
  • 表达式中不含除零运算。
  • 输入是一个根据逆波兰表示法表示的算术表达式。
  • 答案及所有中间计算结果可以用 32 位 整数表示。

注意:减法和除法的操作数顺序需要注意。第2次pop()得到的此时第1个操作数
java

  • 单引号表示字符char,双引号表示字符串String
  • 将字符串转化为数值Integer.valueOf(str)
class Solution {
    public int evalRPN(String[] tokens) {
        Stack<Integer> stack = new Stack<>();
        for(int i = 0; i<tokens.length; ++i){
            if(tokens[i].equals("+"))
                stack.push(stack.pop() + stack.pop());
            else if(tokens[i].equals("-"))
                stack.push( -stack.pop() + stack.pop());
            else if(tokens[i].equals("*"))
                stack.push(stack.pop() * stack.pop());
            else if(tokens[i].equals("/")){
                int temp1 = stack.pop();
                int temp2 = stack.pop();
                stack.push(temp2 / temp1);
            } else{
                stack.push(Integer.valueOf(tokens[i]));
            }
        }
        return stack.pop();
    }
}

6. 滑动窗口最大值

题目:给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
示例

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]

思路

  • 暴力解法:时间复杂度 n*k
  • 单调队列:时间复杂度 <=2n , 空间复杂度 k
  • 本题实现的是单调递减队列(元素值单调递减,但队列存储的是下标)
    • 队首元素即为窗口内最大值的下标
    • 每次从队尾入队,都要把小于nums[i]的元素下标从队列中清除(符合单调递减)
    • for循环后的第一个while循环是确保队列中的下标都在当前窗口内
  • 本题使用的是双端队列ArrayDeque实现的
    • peekFirst(), peekLast()
    • addLast(), addFirst()
    • pollFirst(), pollLast()
    • isEmpty()
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        ArrayDeque<Integer> deque = new ArrayDeque<>();
        int[] result = new int[nums.length - k + 1];
        int result_index = 0;
        // 队列中存放的是下标,i表示滑动窗口的最右端
        for(int i = 0; i<nums.length; ++i) {
            // 把不在窗口内的队首元素弹出
            while(!deque.isEmpty() && deque.peek() < i-k+1){
                deque.pollFirst();
            }
            // 把队尾小于nums[i]的元素,都从队尾弹出
            while(!deque.isEmpty() && nums[i] > nums[deque.peekLast()]){
                deque.pollLast();
            }
            // 把下标i加入队尾
            deque.addLast(i);
            // 从i到达第一个窗口的最右侧开始,每个循环填充一个窗口最大值
            if(i >= k-1){
                result[result_index] = nums[deque.peekFirst()];
                result_index++;
            }
        }
        return result;
    }
}

7. 前K个高频元素(LeetCode 347)

题目:给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
示例

输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

思路

  • top-K的问题适合使用堆来实现
  • 优先队列PriorityQueue的底层数据结构就是堆
  • 如果需要自定义排序的规则需要
    • 实现比较器MyComparator implements Comparator<...>
    • 重写public int compare()函数
    • 在创建 优先队列对象 时将 自定义的比较器对象 传入
  • 本题维护1个大小为k的小根堆,保存当前频数最大的k个[num, num_count]

java

  • 利用HashMap进行常用的频数统计
    HashMap<Integer, Integer> map = new HashMao<>();
    for(int num: nums){
        map.put(i, map.getOrDefault(i, 0) + 1);
    }
    
  • 快速创建数组
    new int[]{num1, num2, ...}
    
class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        HashMap<Integer,Integer> map = new HashMap<>();
        int[] result = new int[k];
        // 统计频数
        for(int i: nums){
            map.put(i, map.getOrDefault(i, 0) + 1);
        }
        // pq中的每个元素形如:[num, num_count]
        // 根据频度排序的、大小为k的小根堆
        PriorityQueue<int[]> pq = new PriorityQueue<>(new MyComparator());
        // 维护小根堆
        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){
            result[i] = pq.poll()[0];
        }
        return result; 
    }
}

// 重写比较器Comparator中的compare函数
class MyComparator implements Comparator<int[]>{
    @Override
    public int compare(int[] pair1, int[] pair2){
        //根据频数进行比较
        return pair1[1] - pair2[1];
    }
}
posted @   Frank23  阅读(25)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示