刷题日记11 | 239. 滑动窗口最大值、347.前 K 个高频元素

113 阅读2分钟

刷题日记11

今天的主题是队列——单调栈——优先级队列!

自定义单调栈

这里我们用Deque作为容器来实现单调栈,关键是要实现队列的push()方法和poll()方法。

push()方法需要让加入的值大于队列最后一个元素,如果最后一个元素小于加入值,则弹出最后一个元素。这样就能维护单调递减了。

poll()方法需要弹出正确的值,这是因为我们的单调队列不是保存了所有值的队列,里面的元素有可能已经在执行push()时因为小于添加值而被弹出了。注意单调队列是因题而异的,因为这道题是滑动窗口,我们需要弹出窗口a,[b,c]前的元素,也就是a。如果单调队列的最大值也就是第一个元素等于我们想弹出的值,也就是a,则弹出。

优先级队列

什么是优先级队列呢?

其实就是一个披着队列外衣的堆,因为优先级队列对外接口只是从队头取元素,从队尾添加元素,再无其他取元素的方式,看起来就是一个队列。

堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。  如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆。

239. 滑动窗口最大值

很显然这道题不能用暴力方法求解,因为暴力解法复杂度为O(n*k)会超时。

这就需要用到单调栈了,单调栈指的是单调递增或者单调递减的队列。我们可以自己写一个单调栈,让它保持单调递减,这样队列最前面的值就是最大值。

class MyQueue {
    private Deque<Integer> queue;
    public MyQueue(){
        queue = new LinkedList<>();
    }
    public void poll(int n){
        if(!queue.isEmpty() && queue.peekFirst() == n){
            queue.pollFirst();
        }
    }
    public void push(int n){
        while(!queue.isEmpty() && queue.peekLast() < n){
            queue.pollLast();
        }
        queue.offerLast(n);
    }
    public int peek(){
        return queue.peekFirst();
    }
}
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        List<Integer> res = new LinkedList<>();
        MyQueue q = new MyQueue();
        // 先将前k的元素放进队列
        for(int i = 0; i < k; i++){
            q.push(nums[i]);
        }
        res.add(q.peek());
        // 从k开始遍历,此时滑动窗口为[i-k+1, k]
        for(int i = k; i < nums.length; i++){
            // 弹出nums[i-k]元素
            q.poll(nums[i-k]);
            q.push(nums[i]);
            res.add(q.peek());
        }
        return res.stream().mapToInt(x -> x).toArray();
    }
}

347. 前 K 个高频元素

这道题使用的是优先级队列(堆),以后遇到类似的题目,前n个最大值,第n个最大值,这样的题目都可以用优先级队列。

那么为什么要使用小顶堆呢?这是因为大顶堆需要对所有元素(的频率)排序,而小顶堆只需要排序k个元素,小顶堆的时间复杂度更优。

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> map = new HashMap<>();
        PriorityQueue<Integer> pq = new PriorityQueue<>((m1, m2) -> map.get(m1) - map.get(m2));
        for(int i = 0; i < nums.length; i++){
            map.put(nums[i], map.getOrDefault(nums[i], 0) + 1);
        }
        //在优先队列中存储二元组(num,cnt),cnt表示元素值num在数组中的出现次数
        //出现次数按从队头到队尾的顺序是从小到大排,出现次数最低的在队头(相当于小顶堆)
        for(Map.Entry<Integer, Integer> entry : map.entrySet()){
            if(pq.size() < k){
                pq.offer(entry.getKey());
            }else if(entry.getValue() > map.get(pq.peek())){
                pq.poll();
                pq.offer(entry.getKey());
            }
        }
        int[] res = new int[k];
        for(int i = 0; i < k; i++){
            res[i] = pq.poll();
        }
        return res;
    }
}