[LeetCode]-队列&优先队列/堆

前言

记录 LeetCode 刷题时遇到的 队列 以及 优先队列相关题目

队列

225.用队列实现栈

用两个队列实现栈。建立两个队列,队列1和队列2。数据输入时输入到其中一个队列,要取出数据时,将这个有数据的队列的元素一个一个出队并进入另一个队列,直到这个有数据的队列剩下最后一个元素时就是整个栈的栈顶元素,如果是要出栈,直接把这最后一个元素出队就可以了;如果是要获取栈顶元素,那就要先用一个Integer变量存这个元素的值,然后把这个元素加入另一个队列,再把这个值返回

所以可以看出来,两个队列,除了在初始化整个栈以及弹出所有栈中元素时都为空队列,其他时候两个队列中只会有一个队列不含元素。在入栈,出栈以及获取栈顶元素时要先判断

class MyStack {
    
    
    private LinkedList<Integer> queue1;
    private LinkedList<Integer> queue2;
    public MyStack() {
    
    
        queue1 = new LinkedList<>();
        queue2 = new LinkedList<>();
    }
	//入栈
    public void push(int x) {
    
    
        if(queue1.peek() == null){
    
    
            queue2.offer(x);
        }else {
    
    
            queue1.offer(x);
        }
    }
	//出栈
    public int pop() {
    
    
        if(queue1.peek() == null){
    
    
            if(queue2.peek() == null){
    
    
                return -1;
            }else {
    
    
                while (queue2.size() > 1){
    
    
                    queue1.offer(queue2.poll());
                }
                return queue2.poll();
            }
        }else {
    
    
            while (queue1.size() > 1){
    
    
                queue2.offer(queue1.poll());
            }
            return queue1.poll();
        }
    }
	//获取栈顶元素
    public int top() {
    
    
        if(queue1.peek() == null){
    
    
            if(queue2.peek() == null){
    
    
                return -1;
            }else {
    
    
                while (queue2.size() > 1){
    
    
                    queue1.offer(queue2.poll());
                }
                Integer value = queue2.peek();
                queue1.offer(queue2.poll());
                return  value;
            }
        }else {
    
    
            while (queue1.size() > 1){
    
    
                queue2.offer(queue1.poll());
            }
            Integer value = queue1.peek();
            queue2.offer(queue1.poll());
            return value;
        }
    }
	//判断栈中是否为空
    public boolean empty() {
    
    
        return queue1.size() == 0 && queue2.size() == 0;
    }
}

其实一个队列就可以实现一个栈了,在获取栈顶元素时,把队列中除了最后一个元素之外所有元素出队再入队(即变到队尾),使原本队列中最后一个元素变到队头位置就可以了

239.滑动窗口最大值-单调队列

单调队列, 就是在原先的队列基础上,规定队列中的元素必须是单调递增或单调递减的,只需要满足这一个规定,具体这个单调队列的某些操作,细节,比如元素的出队或入队等,如何实现要看具体情况

本题中,窗口每次移动一位,单调队列中放的就是所有可能是当前这个滑动窗口中最大值的元素,未处理因移动而带来的新数组元素时,我们使队列 单调递减,这样队列头元素就是当前队列中的最大元素,然后对新出现的元素处理,把队列尾所有小于这个新元素的元素出队,再让新元素入队 (即作为新的队尾元素),这样新的队列也还是递减顺序,而前面说到队列中放的就是当前这个滑动窗口 (这个新元素和它往前的 k - 1 个元素) 中可能是最大值的元素,那最大值肯定就是队列头元素了。每移动一次窗口,即出现一个新的元素都这么操作就可以了

不过要保证当前队列放的是可能是当前窗口最大值的元素,那就要先保证队列中所有元素是存在当前这个窗口中的,所以代码中的队列放的是元素在数组中的下标,当遍历到下标 i 的元素时,队列中所有元素的下标必须是 [i - k + 1,i] 才符合,所以队列不能直接存数组元素,而是要存元素的下标索引

public int[] maxSlidingWindow(int[] nums, int k) {
    
    
    LinkedList<Integer> monotonousQueue = new LinkedList<>();
    //先对前k个元素进行处理,初始化单调队列
    for (int i = 0; i < k; i++) {
    
    
        while (!monotonousQueue.isEmpty() && nums[monotonousQueue.peekLast()] < nums[i]){
    
    
            monotonousQueue.removeLast();
        }
        monotonousQueue.offer(i);
    }
    int[] res = new int[1 + nums.length - k];
    int index = -1;
    for(int i = k;i < nums.length;i++){
    
    
        res[++index] = nums[monotonousQueue.peek()];
        //排除掉不可能存在当前窗口的元素
        while (!monotonousQueue.isEmpty() && monotonousQueue.peek() < i - k + 1){
    
    
            monotonousQueue.poll();
        }
        while (!monotonousQueue.isEmpty() && nums[monotonousQueue.peekLast()] < nums[i]){
    
    
            monotonousQueue.removeLast();
        }
        monotonousQueue.offer(i);
    }
    //循环中最后一个元素处理完后,它的窗口的最大值没有被放入结果数组
    res[++index] = nums[monotonousQueue.peek()];
    return res;
}

优先队列/堆

692. 前K个高频单词

对于这种前k个,k个最,第k大等类似的问题,都可以使用 优先队列 来完成

public List<String> topKFrequent(String[] words, int k) {
    
    
//先构建一个字符串与其出现次数的map
    Map<String, Integer> map = new HashMap<String, Integer>();
    for (String word : words) {
    
    
        map.put(word, map.getOrDefault(word, 0) + 1);
    }
    //将每个键值对添加到优先队列中
    PriorityQueue<Map.Entry<String, Integer>> pq = new PriorityQueue<>((entry1,entry2) -> {
    
    
        //使用lambda表达式创建一个函数式接口comparator的对象
        //如果出现了相同次数,就按字典序升序;否则按出现次数的降序
        return entry1.getValue().equals(entry2.getValue()) ? entry1.getKey().compareTo(entry2.getKey()) : entry2.getValue() - entry1.getValue();
    });
    pq.addAll(map.entrySet()); //map.enttrySet().forEach(pq::add);
    List<String> res = new ArrayList<String>();
    //取前k个
    for(int i = 0;i < k;i++){
    
    
        res.add(pq.poll().getKey());
    }
    return res;
}

295. 数据流的中位数

我们使用两个堆 big 以及 small,分别为大顶堆和小顶堆,将数据流中的数据放到这两个堆中
假如我们维持这样的 不变关系

当数据流中的元素个数为偶数时,两个堆所含元素的数量相等;
当数据流中的元素个数为奇数时,big 所含元素数量比 small 多 1

那么,当数据流中的元素个数为偶数时,数据流的中位数就是两个堆的堆顶元素的平均值;当数据流中的元素个数为奇数时,数据流的中位数就是 big 堆的堆顶元素

扫描二维码关注公众号,回复: 16538425 查看本文章

那么,我们只需要在 addNums 时去维护这个不变关系,就能保证在需要获取中位数时可以使用 O(1) 的时间就获取到

class MedianFinder {
    
    
    PriorityQueue<Integer> big = new PriorityQueue<>((a,b) -> b - a);
    PriorityQueue<Integer> small = new PriorityQueue<>((a,b) -> a - b);
    public void addNum(int num) {
    
    
        int bigSize = big.size();
        int smallSize = small.size();
        if(bigSize == smallSize){
    
     //1. 两个堆大小相等,此时我们要让big中比small多一个元素
            //1.1 如果有一个堆为空,说明两个都为空,此时要加入第一个元素,直接加到big中即可;
            //    如果两个堆不为空,那么如果num小于等于big的堆顶元素,就加到big中即可
            if(bigSize == 0 || num <= big.peek()){
    
     
                big.add(num);
            }else{
    
     //1.2 如果num大于big堆顶元素,说明num应该加到small中,那么为了让big中比small多一个元素,就要取出small的堆顶元素到big中
                small.add(num);
                big.add(small.poll());
            }
        }else{
    
     //2. 两个堆大小不相等,说明此时big中比small中多一个元素,我们要让big跟small中元素数量相等
            //2.1 如果num大于big堆顶元素,就加到small中即可
            if(num > big.peek()){
    
    
                small.add(num);
            }else{
    
    
                //2.2 如果num小于等于big堆顶元素,就应该把num加到big中,然后把big的堆顶元素加到small中从而两个堆数量相等
                big.add(num);
                small.add(big.poll());
            } 
        }
    }
    public double findMedian() {
    
    
        int bigSize = big.size();
        int smallSize = small.size();
        if(bigSize == smallSize){
    
    
            if(bigSize == 0){
    
    
                return 0;
            }else{
    
    
                return (big.peek() + small.peek()) / 2.0; //注意,平均值要取小数
            }
        }else{
    
    
            return big.peek();
        }
    }
}

猜你喜欢

转载自blog.csdn.net/Pacifica_/article/details/126442622