LeetCode刷题学习-栈与队列篇


用栈实现队列

栈的特性:先进后出

队列特性:先进先出

因此利用两个栈实现一个队列,一个为输入栈,另一个为输出栈

  • push的时候就直接进入输入栈

  • pop的时候若输出栈为空则需先把进栈数据全部输入来,然后再出栈;若输出栈非空,则直接出栈;

  • 当两个栈均为空时证明队列已空

import java.util.Stack;

public class MyQueue {

    // 输入栈
    Stack<Integer> stackIn;
    // 输出栈
    Stack<Integer> stackOut;

    public MyQueue() {
        stackIn = new Stack<>();
        stackOut = new Stack<>();
    }

    // 输入栈直接push
    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() {
        if(stackOut.isEmpty()) {
            while (!stackIn.isEmpty()) {
                stackOut.push(stackIn.pop());
            }
        }
        return stackOut.peek();
    }

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

用队列实现栈

  1. 双队列实现栈

示例

import java.util.LinkedList;
import java.util.Queue;

public class MyStack {
    // queue1模仿栈
    Queue<Integer> queue1;
    // queue2 做辅助队列
    Queue<Integer> queue2;

    public MyStack() {
        queue1 = new LinkedList<Integer>();
        queue2 = new LinkedList<Integer>();
    }

    public void push(int x) {
        // 新元素先放入辅助队列中
        queue2.offer(x);
        // 当queue1中存在元素时,全部出队加入queue2中
        while (!queue1.isEmpty()) {
            queue2.offer(queue1.poll());
        }
        // 交换两队列,确保queue2每次开始都是空队列,而queue1的队头刚好就是栈顶元素
        Queue<Integer> temp = queue1;
        queue1 = queue2;
        queue2 = temp;
    }

    // 此后每次出栈,获取栈顶元素或判空都只需要操作queue1即可
    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. 单队列实现栈

示例

import java.util.LinkedList;
import java.util.Queue;

public class MyStack {
    Queue<Integer> queue;

    public MyStack() {
        queue = new LinkedList<Integer>();
    }

    public void push(int x) {
        //  每次都获取当前队列的长度
        int size = queue.size();
        // 新元素入队
        queue.offer(x);
        // 然后除队尾的新元素外,所有元素出队再入队,重新排列,这样队头就是栈顶元素
        while (size > 1) {
            queue.offer(queue.poll());
            size--;
        }
    }

    public int pop() {
        return queue.poll();
    }

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

    public boolean empty() {
        return queue.isEmpty();
    }
}

有效的括号

三种可能不匹配的情况:

  1. 左方向有多余括号
  2. 右方向有多余括号
  3. 括号类型不匹配

示例

public boolean isValid(String s) {
    Stack<Character> stack = new Stack<>();
    char[] eachChar = s.toCharArray();
    for (int i = 0; i < s.length(); i++) {
        // 遍历到左括号,把相应右括号入栈
        if (eachChar[i] == '(') {
            stack.push(')');
        } else if (eachChar[i] == '{') {
            stack.push('}');
        } else if (eachChar[i] == '[') {
            stack.push(']');
        }
        // 当字符串尚未被遍历完而栈为空时,说明右方向有多余括号
        // 当栈顶元素与当前遍历字符不等时,说明有括号类型不匹配
        else if (stack.isEmpty() || stack.peek() != eachChar[i]) {
            return false;
        } else {
            // 匹配到相应的左括号,相应的右括号出栈
            stack.pop();
        }
    }
    // 遍历完毕后,栈也应该为空
    return stack.isEmpty();
}

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

解题思想:利用栈先入后出的特性

当栈为空或栈顶元素和当前遍历的字符不相等时,则入栈

当栈顶元素和当前遍历的字符相等时,则出栈

最后栈中所剩元素即删除相邻重复项后所剩下的字符串

public String removeDuplicates(String s) {
    Stack<Character> stack = new Stack<>();
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < s.length(); i++) {
        // 栈空或不等,入栈
        if (stack.isEmpty() || s.charAt(i) != stack.peek()) {
            stack.push(s.charAt(i));
        } else {
            // 相邻元素相同,出栈,往后走
            stack.pop();
        }
    }
    // 栈中所剩则所求
    while (!stack.isEmpty()) {
        sb.append(stack.pop());
    }
    return sb.reverse().toString();
}

逆波兰表达式求值

class Solution {
    public int evalRPN(String[] tokens) {
        // 利用栈的特性
        Stack<String> stack = new Stack<>();
        // 循环遍历数组内容
        for (String token : tokens) {
            // 当栈为空或数组元素是数字时,入栈
            if (stack.isEmpty() || !"+-*/".contains(token)) {
                stack.push(token);
            }
            // 数组元素是运算符时,栈中数字出栈,结合运算符进行运算,结果入栈
            else if ("+-*/".contains(token)) {
                int first = Integer.parseInt(stack.pop());
                int second = Integer.parseInt(stack.pop());
                int result = Integer.MAX_VALUE;
                switch (token) {
                    case "+":
                        result = second + first;
                        break;
                    case "-":
                        result = second - first;
                        break;
                    case "*":
                        result = second * first;
                        break;
                    case "/":
                        result = second / first;
                        break;
                }
                stack.push(String.valueOf(result));
            }
        }
        // 最后栈中所剩即结果
        return Integer.parseInt(stack.pop());
    }
}

滑动窗口最大值

注意队列中存储的是下标值,利用双端队列,两边都可进出的特性

思路参考:

https://leetcode.cn/problems/sliding-window-maximum/solution/dong-hua-yan-shi-dan-diao-dui-lie-239hua-hc5u/

  • 遍历给定数组中的元素,如果队列不为空且当前考察元素大于等于队尾元素,则将队尾元素移除。直到,队列为空或当前考察元素小于新的队尾元素;

  • 当队首元素的下标小于滑动窗口左侧边界left时,表示队首元素已经不在滑动窗口内,因此将其从队首移除。

  • 由于数组下标从0开始,因此当窗口右边界right+1大于等于窗口大小k时,意味着窗口形成。此时,队首元素就是该窗口内的最大值。

import java.util.LinkedList;

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        // 滑动窗口的个数
        int[] res = new int[nums.length - k + 1];
        // 双端队列
        LinkedList<Integer> queue = new LinkedList<>();
    
        // 右边界遍历
        for (int right = 0; right < nums.length; right++) {
            // 当队列非空且右边界对应的值比队列中元素大的时候,队列元素出队
            while (!queue.isEmpty() && nums[right] >= nums[queue.peekLast()]) {
                queue.removeLast();
            }
            // 然后右边界对应的值(索引入队)
            queue.addLast(right);
            // 计算对应的左边界
            int left = right - k + 1;
            // 说明队头元素不在滑动窗口内了,元素出队
            if(queue.peekFirst() < left) {
                queue.removeFirst();
            }
            // 右边界到达位置,说明窗口形成,此时队头索引对应的元素即窗口内最大值
            if (right + 1 >= k) {
                res[left] = nums[queue.peekFirst()];
            }
        }
        return res;
    }
}

前 K 个高频元素

利用优先级队列实现

补充知识:

优先级队列,其实就是一个披着队列外衣的堆,其底层实现就是利用了堆的数据结构

关于

Comparator接口说明:
 * 返回负数,形参中第一个参数排在前面;返回正数,形参中第二个参数排在前面
 * 对于队列:排在前面意味着往队头靠
 * 对于堆(使用PriorityQueue实现):从队头到队尾按从小到大排就是最小堆(小顶堆),
 * 从队头到队尾按从大到小排就是最大堆(大顶堆)--->队头元素相当于堆的根节点
import java.util.HashMap;
import java.util.PriorityQueue;

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        // 建立map用于存储每个数的频率次数,key为数组中的值,value为对应出现的次数(即频率)
        HashMap<Integer, Integer> map = new HashMap<>();
        for(int num : nums) {
            if(map.containsKey(num)) {
                map.put(num, map.get(num) + 1);
            } else {
                map.put(num, 1);
            }
        }

        // 优先级队列,默认是小根堆
        // 此处优先队列默认排序的是key(因为自己存进去的是key),因此需要重写确定自己需要的排序依据,此处重写后排序的是value
        PriorityQueue<Integer> queue = new PriorityQueue<>((o1, o2) -> map.get(o1) - map.get(o2));
        // 建堆操作
        for (Integer key : map.keySet()) {
            // 当队列长度小于指定的k个数时,可以继续入队(即可以加入堆中)
            if (queue.size() < k) {
                queue.add(key);
            }
            // 当对应的频率次数大于队列的队头元素(即堆顶元素)时,堆顶元素弹出,新元素入队
            else if(map.get(key) > map.get(queue.peek())) {
                queue.remove();
                queue.add(key);
            }
        }
        // 最终队列中所剩的就是频率最大的前k个
        int[] result = new int[queue.size()];
        for (int i = 0; i < result.length; i++) {
            result[i] = queue.poll();
        }
        return result;
    }
}

评论
  目录