leetcode总结 DAY10 - 13 栈和队列

DAY 10 栈和队列1

232 用栈实现队列

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(pushpoppeekempty):

实现 MyQueue 类:

  • void push(int x) 将元素 x 推到队列的末尾
  • int pop() 从队列的开头移除并返回元素
  • int peek() 返回队列开头的元素
  • boolean empty() 如果队列为空,返回 true ;否则,返回 false
class MyQueue {
    private Stack<Integer> stack1;
    private Stack<Integer> stack2;

    public MyQueue() {
        stack1 = new Stack<>(); // 入
        stack2 = new Stack<>(); // 出
    }
    
    public void push(int x) {
        stack1.push(x);
    }
    
    public int pop() {
        if(stack2.isEmpty()){
            while(!stack1.isEmpty()){
                stack2.push(stack1.pop());
            }
        }
        return stack2.pop();
    }
    
    public int peek() {
        int num = pop();
        stack2.push(num);
        return num;
    }
    
    public boolean empty() {
        // if(stack1.size()==0 && stack2.size()==0){
        //     return true;
        // }
        // return false;
        return stack1.isEmpty() && stack2.isEmpty();
    }
}

225 用队列实现栈

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(pushtoppopempty)。

实现 MyStack 类:

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

两个队实现栈:

利用两个队,将队列里的顺序调整成栈里的顺序

class MyStack {
    private Queue<Integer> queue1;
    private 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());
        }
        Queue<Integer> tem = queue1;
        queue1 = queue2;
        queue2 = tem;
    }
    
    public int pop() {
        return queue1.poll();
    }
    
    public int top() {
        return queue1.peek();
    }
    
    public boolean empty() {
        return queue1.isEmpty();
    }
}

一个队实现栈:

class MyStack {
    private Queue<Integer> queue;

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

DAY 11 栈和队列2 匹配类问题

20 有效的括号

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

有效字符串需满足:

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

解题思路:

tips:要写代码之前要分析好有哪几种不匹配的情况,如果不动手之前分析好,写出的代码也会有很多问题。

先找到所有匹配错误情况:
1. 字符串里左括号多余 —— 字符串空了,栈没空
1. 括号没有多余,但是 括号的类型没有匹配上 —— 字符串中右括号和栈中左括号不匹配
1. 字符串里右括号多余 —— 栈空了,字符串没空

遇见左括号直接存入栈,遇见右括号时再判断是否与栈顶的左括号匹配:

    public boolean isValid(String s) {
        Stack<Character> stack = new Stack<>();
        for(char c : s.toCharArray()){
            if(c=='(' || c=='[' || c=='{') stack.push(c);
            else if(stack.isEmpty()) return false; // 右半括号多了
            else{
                // 左右括号不匹配
                char top = stack.pop();
                if((c==')' && top != '(') || (c==']' && top != '[') || (c=='}' && top != '{')) return false; 
            }
        }
        // 若stack不空,则左半括号多了
        return stack.isEmpty();
    }

遇见左括号将匹配的右括号存入栈,遇见右括号时直接判断是否与栈顶元素相同:

    public boolean isValid(String s) {
        Stack<Character> stack = new Stack<>();
        for(char c : s.toCharArray()){
            if(c=='(' ) stack.push(')');
            else if(c=='[') stack.push(']');
            else if(c=='{') stack.push('}');
            else if(stack.isEmpty() || c!=stack.pop()) return false; // 右半括号多了 或 括号类型不匹配
        }
        // 若stack不空,则左半括号多了
        return stack.isEmpty();
    }

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

给出由小写字母组成的字符串 S重复项删除操作会选择两个相邻且相同的字母,并删除它们。

在 S 上反复执行重复项删除操作,直到无法继续删除。

在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。

借助栈Stack实现:

    public String removeDuplicates(String s) {
        Stack<Character> stack = new Stack<>();
        for(char c : s.toCharArray()){
            if(!stack.isEmpty() && c==stack.peek()){
                stack.pop();
                continue;
            }
            stack.push(c);
        }
        StringBuilder res = new StringBuilder();
        // 注意:栈中顺序和字符串顺序相反,栈顶元素反而在字符串最后
        while(!stack.isEmpty()){
            res.insert(0,stack.pop());
        }
        return res.toString();
    }

将字符串作为栈实现:

    public String removeDuplicates(String s) {
        StringBuilder res = new StringBuilder();
        for(char c : s.toCharArray()){
            int len = res.length();
            if(len>0 && c==res.charAt(len-1)) res.deleteCharAt(len-1);
            else res.append(c);
        }
        return res.toString();
    }

150 逆波兰表达式求值

有效的算符包括 +-*/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

注意 两个整数之间的除法只保留整数部分。

可以保证给定的逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

定义:

逆波兰表达式:是一种后缀表达式,所谓后缀就是指算符写在后面。

​ 平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。

​ 该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。

逆波兰表达式主要有以下两个优点:

  • 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
  • 适合用栈操作运算:遇到数字则入栈遇到算符取出栈顶两个数字进行计算,并将结果压入栈中。

思路:

递归就是用栈来实现的。所以栈与递归之间在某种程度上是可以转换的!

**逆波兰表达式相当于是二叉树中的后序遍历:**逆波兰表达式是用后续遍历的方式把二叉树序列化了

在进一步看,本题中每一个子表达式要得出一个结果,然后拿这个结果再进行运算,那么**这岂不就是一个相邻字符串消除的过程,和 1047.删除字符串中的所有相邻重复项 中的对对碰游戏非常像了。**只不过本题不要相邻元素做消除了,而是做运算!

    public int evalRPN(String[] tokens) {
        Deque<Integer> stack = new LinkedList<>();
        for(String s : tokens){
            if(!("*".equals(s) || "/".equals(s) || "+".equals(s)|| "-".equals(s))){
                stack.push(Integer.valueOf(s));
            }else{
                // 注意!!! n1 和 n2 的顺序,n1是减数和除数,n2才是被减数和被除数
                int n1 = stack.pop();
                int n2 = stack.pop();
                int curRes = 0;
                if("*".equals(s)) curRes = n2 * n1;
                else if("/".equals(s)) curRes = n2 / n1; 
                else if("+".equals(s)) curRes = n2 + n1;
                else curRes = n2 - n1;
                stack.push(curRes);
            }
        }
        return stack.pop();
    }

DAY 13 栈和队列3

239 滑动窗口最大值

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

返回 滑动窗口中的最大值

单调队列

设计单调队列的时候,poll 和 offer 操作要保持如下规则:

​ 队列前端为出口,队列后端为入口

  1. poll(value):如果滑动窗口移除的元素value等于单调队列的出口元素,那么队列弹出元素,否则不用任何操作
  2. offer(value):如果offer的元素value大于入口元素的数值,那么就将队列入口的元素弹出,直到offer元素的数值小于等于队列入口元素的数值为止
  3. getMaxValue():获取队列出口元素。单调队列中出口元素就是窗口里最大元素
实现

**法一:**利用双端队列完成单调队列操作

    public int[] maxSlidingWindow(int[] nums, int k) {
        Deque<Integer> queue = new LinkedList<>();
        int[] res = new int[nums.length-k+1];

        int max = Integer.MIN_VALUE;
        for(int i=0;i<nums.length;i++){
            // poll: 判断当前队列中队首的值是否有效 
            if(i>=k && nums[i-k] == queue.peekFirst()) queue.pollFirst();
            // offer: 保证队中元素从大到小 如果前面数小则需要依次弹出,直至满足要求
            while(!queue.isEmpty() && queue.peekLast()<nums[i]){
                queue.pollLast();
            }
            queue.offerLast(nums[i]);
            // getMaxValue: 当窗口长度为k时 保存当前窗口中最大值
            if(i+1 >= k) res[i+1-k]=queue.peekFirst();
        }
        return res;
    }

**法二:**自定义一个类实现单调队列

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

    void poll(int val){
        if(!queue.isEmpty() && queue.peekFirst()==val) queue.pollFirst();
    }

    void offer(int val){
        while(!queue.isEmpty() && queue.peekLast()<val){
            queue.pollLast();
        }
        queue.offerLast(val);
    }

    int getMaxVal(){
        return queue.peekFirst();
    }
}


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

        for(int i=0;i<k;i++){
            que.offer(nums[i]);
        }
        res[0] = que.getMaxVal();
        
        for(int i=k;i<nums.length;i++){
            que.poll(nums[i-k]);

            que.offer(nums[i]);
            res[i-k+1] = que.getMaxVal();
        }
        return res;
    }
}

**Tips:**单调队列不是一成不变的,而是不同场景不同写法,总之要保证队列里单调递减或递增的原则,所以叫做单调队列。

347 前 K 个高频元素

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

优先级队列

使用不弹出元素(即n个元素)的优先级队列(大or小顶堆),相当于对n个元素进行排序,时间复杂度是O(nlogn)

使用仅含k个元素的优先级队列(大or小顶堆),时间复杂度是O(nlogk):遍历一遍需要O(n)的时间复杂度,每次需要对k个元素排序,时间复杂度是O(logk)

所以,本题使用仅含k个元素的小顶堆(优先级队列)更优

左大于右就会建立小顶堆,左小于右建立大顶堆

实现

java中默认的优先级队列是小顶堆

Queue<T> p = new PriorityQueue<>()
PriorityQueue<T> p = new PriorityQueue<>()

Comparable接口与Comparator接口使用的对比

  1. Comparable是令需要比较大小的类实现接口,一旦设置好之后,可以保证该实现类的对象在任何位置都可以比较大小
    Comparator是临时设置的排序方式,什么时候需要比较,就临时创建一个Comparator的实现类

  2. 调用Comparable中的compareTo方法是用其实现类的对象去调用的,参数是另一个需要对比的对象。name1.compareTo(name2);
    调用Comparator中的compare()方法是用其实现类(如Double)去调用的,参数是两个需要对比的对象。Double.compare(d1,d2);

  3. Comparator接口说明: 对 o1 - o2
    返回负数,即o1<o2,o1排在前面;返回正数,即o1>o2,o2排在前面,即,o1 - o2 从小到大排序

    若将结果取反,就有o1<o2,o2在前;o1>o2,o1在前

    即,-(o1-o2) = o2-o1 从大到小排序

优先级队列 搭配 Comparator 比较器

注意:重写比较器Comparator时,比较器也要明确指定泛型类型

Queue<T> queue  = new PriorityQueue<>(new Comparator<T>(){
    public int compare(T o1, T o2){
        return o1-o2;
    }
});

PriorityQueue<T> queue = new PriorityQueue<>( (o1,o2) -> o1-o2 );

具体代码:

    public int[] topKFrequent(int[] nums, int k) {
        Map<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);
        }
        
        // Queue<int[]> queue  = new PriorityQueue<>(new Comparator<int[]>(){
        //     public int compare(int[] o1, int[] o2){
        //         return o1[1]-o2[1];
        //     }
        // });
        PriorityQueue<int[]> queue = new PriorityQueue<>( (o1,o2) -> o1[1]-o2[1] );

        for(Map.Entry<Integer,Integer> entry : map.entrySet()){
            queue.offer(new int[]{entry.getKey(), entry.getValue()});
            if(queue.size() > k) queue.poll();
        }

        int[] res = new int[k];
        //依次弹出小顶堆,先弹出的是堆的根,出现次数少,后面弹出的出现次数多
        for(int i=0;i<k;i++){
            res[i] = queue.poll()[0];
        }
        return res;
    }
优先级队列 搭配 Comparable接口
class Solution {
    public int[] topKFrequent(int[] nums, int k) {
       int[] ret = new int[k];
        // 边界条件
        if(nums==null || k==0){
            return ret;
        }

        Map<Integer,Integer> map = new HashMap<>();
        // 1. 遍历数组,将出现的元素及其频次保存到Map集合中
        for (int i = 0; i < nums.length; i++) {
            // 哈希表中不含nums[i]为key的键值对
            if(!map.containsKey(nums[i])){
                map.put(nums[i],1);
            }else {
                // 哈希表中含nums[i]为key的键值对
                int times = map.get(nums[i]);
                map.put(nums[i],times+1);
            }
        }
        // 2. 扫描Map集合,将出现频次最高的前k个元素添加到优先级队列中
        Queue<Node> queue = new PriorityQueue<>();
        for(Map.Entry<Integer,Integer> entry: map.entrySet()){
            // 优先级队列中元素个数小于等于k时
            if(queue.size()<k){
                queue.offer(new Node(entry.getKey(), entry.getValue()));
            }else {
                // 元素中个数大于k时
                Node node = queue.peek();
                // 将频次高的换入队列中
                if(entry.getValue()> node.value){
                    queue.poll();
                    queue.offer(new Node(entry.getKey(), entry.getValue()));
                }
            }
        }
        // 将队列里频次高的前k个元素导入ret数组
        int i = 0;
        while (!queue.isEmpty()){
            ret[i] = queue.poll().key;
            i++;
        }
        return ret;
    }
        
}

class Node implements Comparable<Node>{
    // 数组中出现的元素
    int key;
    // 该元素出现的频次
    int value;

    public Node(int key, int value){
        this.key = key;
        this.value = value;
    }
    // 告诉优先级队列Node对象如何比较
    @Override
    public int compareTo(Node o) {
        return this.value-o.value;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值