leetcode刷题篇 第三篇

97 阅读3分钟

今天是lc刷题第三篇

主题:栈,队列 方式:题目记录

栈和队列的相互实现

  1. 使用栈去实现队列

思路:

使用两个栈,入了第一个栈之后pop出来入第二个栈

再执行pop操作即可

  1. 使用队列去实现栈

可以使用两个队列,但是比较简单,在这儿就不说了

这里说下使用一个队列去实现栈:首先先将所有元素入队,之后将队列中的(size - 1) 依次推出重新入队,之后再进行出队操作即可

有效的括号

需求分析:对字符串进行匹配

特征:前后匹配 ---> 解法:使用栈

在做本题之前补充一个小知识:

思考流程:对于这类题,优先考虑返回false的情况,其余均为true


class Solution {

    public boolean isValid(String s) {

        // 需求:对字符串进行匹配 特征 前后匹配 ---> 栈

        // 考虑不匹配情况,其余均返回true

        if (s.length() % 2 != 0){

            return false;

        }

        Deque<Character> stack = new LinkedList<>();

        for (int i = 0; i < s.length(); i++) {

            char ch = s.charAt(i);

            if (ch == '(') {

                stack.push(')');

            }else if (ch == '{') {

                stack.push('}');

            }else if (ch == '[') {

                stack.push(']');

            }else if(stack.isEmpty() || ch != stack.peek()){

                return false;

            } else{

                stack.pop();

            }

        }

        boolean isEmpty = stack.isEmpty();

        return isEmpty;

    }

}

也可以考虑使用双指针的方式去模仿栈,提高效率


class Solution {

    public String removeDuplicates(String s) {

        // 需求:消除相邻相同元素

        // 特征:相邻匹配问题

        // 如何遍历一个栈

        char[] chars = s.toCharArray();

        int slow = 0,fast = 0;

        while(fast < chars.length){

            chars[slow] = chars[fast];

            if(slow > 0 && chars[slow] == chars[slow - 1]){

                // 进行下一轮时 slow-- 位置的字符会被覆盖

                slow--;

            }else {

                slow++;

            }

            fast++;

        }

        return new String(chars,0,slow);

    }

}

逆波兰表达式求值

后缀表达式切换为中序遍历方式的表达式

其中需要进行添加括号操作,数字前后均需要添加括号,但是我们也不能提起放置好括号在栈,因为我们不知都后续的操作符会是什么

那么此时,考虑到的方法就是,将数字取出,进行额外处理,但是这里还要进行下一步处理,就是将这几个字符绑定成一个新的字符重新放入栈中,方便后续操作

但是本题只需要一个结果那么直接拉出来操作返回数字即可,不需要考虑优先级,但是别忘了-和除是需要进行特殊处理的


class Solution {

    public int evalRPN(String[] tokens) {

        Deque<Integer> stack = new LinkedList();

        for (String s : tokens) {

            if ("+".equals(s)) {

                stack.push(stack.pop() + stack.pop());

            } else if ("-".equals(s)) {

                // 注意 - 和/ 需要特殊处理

                stack.push(-stack.pop() + stack.pop());

            } else if ("*".equals(s)) {

                stack.push(stack.pop() * stack.pop());

            } else if ("/".equals(s)) {

                int temp1 = stack.pop();

                int temp2 = stack.pop();

                stack.push(temp2 / temp1);

            } else {

                stack.push(Integer.valueOf(s));

            }

        }

        return stack.pop();

    }

}

滑动窗口最大值

特征:一个不断变化的局部 且需要维护其中的数据 ————> 单调队列

根据需求,需要考虑的就是单调队列中每次弹出的数据需要是最大的 但是还需要保证在动态变化,每次需要动态的进行元素的变动 即元素的进出

那么,如何对队列安排进出条件才可以呢?

我们回到需求中,需求每次在局部中获取到最大值并返回

队列中获取元素最简单的方式就是获取第一个,需要进行排序,但是排序之后就无法满足弹出元素这个条件

那么此时我们可以想到也许不需要维护所有的元素,只留下局部中较大的值即可

那么此时我们自己创建这样一个数据结构

里面有三个方法

  1. push函数 对比传入元素val 若大,就将队列中所有比他小的推出 若小,就留下

  2. pop函数 对比当前传入元素是否为被移动出窗口的元素,若是则推出 若不是则继续

  3. peek函数 获取队头元素


class MyQueue {

    Deque<Integer> deque = new LinkedList<>();

    //

    void pop(int val) {

        if (!deque.isEmpty() && val == deque.peek()) {

            deque.poll();

        }

    }

    void push(int val) {

        while (!deque.isEmpty() && val > deque.getLast()) {

            deque.removeLast();

        }

        deque.add(val);

    }

    int peek() {

        return deque.peek();

    }

}

class Solution {

    public int[] maxSlidingWindow(int[] nums, int k) {

        // 需求:每次获取一个局部,取得最大值

        // 特征:一个不断变化的局部 且需要维护其中的数据

        MyQueue queue = new MyQueue();

        if (nums.length == 1) {

            return nums;

        }

        int len = nums.length - k + 1;

        int[] res = new int[len];

        int num = 0;

        for (int i = 0; i < k; i++) {

            queue.push(nums[i]);

        }

        res[num++] = queue.peek();

        for (int i = k; i < nums.length; i++) {

            queue.pop(nums[i - k]);

            queue.push(nums[i]);

            res[num++] = queue.peek();

        }

        return res;

    }

}

前K个高频元素

需求:

  1. 对每个元素进行频次统计

  2. 进行排序

  3. 获取排序之后的结果

主要需求为前面二者

特征:大数值获取高频低频

对应解法:使用优先级队列(底层为堆)(数据结构特点是有序)+ map存储

本题采用大根堆小根堆的方式均可

  • 大根堆时间复杂度为O(nlogn)

  • 小根堆时间复杂度为O(nlogk)


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);

        }

        // 这里使用lambda表达式的方式去写比较器

        // 将map传入 转换为数组,对比的是每一个value 这里是小根堆方式 (若为pair2 - pair1)则为大根堆

        PriorityQueue<int[]> pq = new PriorityQueue<>((pair1,pair2)->pair1[1]-pair2[1]);

  


        for(Map.Entry<Integer,Integer> entry:map.entrySet()){

            if(pq.size()<k){//小顶堆元素个数小于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()});

                }

            }

        }

        int[] res = new int[k];

        for(int i=k-1;i>=0;i--){//依次弹出小顶堆,先弹出的是堆的根,出现次数少,后面弹出的出现次数多

            res[i] = pq.poll()[0];

        }

        return res;

    }

}