代码随想录Day12|栈与队列part3|239. 滑动窗口最大值、347.前 K 个高频元素、总结

239. 滑动窗口最大值

题目链接:https://leetcode.com/problems/sliding-window-maximum/

You are given an array of integers nums, there is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position.
Return the max sliding window. 线性时间复杂度?
Input: nums = [1,3,-1,-3,5,3,6,7], k = 3
Output: [3,3,5,5,6,7]

class Solution {
    class Myqueue {
        // 自定义queue队列,单调递减队列,但不仅是排序(对比优先队列)
        Deque<Integer> deque = new LinkedList<>();
    //弹出元素时,比较当前要弹出的数值是否等于队列出口的数值,如果相等则弹出
    //同时判断队列当前是否为空
        void poll(int val) {
            if (!deque.isEmpty() && deque.peek() == val){
                deque.poll();
            }
        }

    //添加元素时,如果要添加的元素大于入口处的元素,就将入口元素弹出
    //保证队列元素单调递减
    //比如此时队列元素3,1,2将要入队,比1大,所以1弹出,此时队列:3,2
        void push(int val) {
            while (!deque.isEmpty() && val > deque.getLast()) {
                deque.removeLast();
            }
            deque.addLast(val);
            // deque.offer(val); // add(val);
        }

    //队列队顶元素始终为最大值
        int peek() {
            return deque.peek();
        }

    }
    public int[] maxSlidingWindow(int[] nums, int k) {
        int count = nums.length - k + 1;
        int[] res = new int[count];
        Myqueue que = new Myqueue();
        for (int i = 0; i < k; i ++) {
            que.push(nums[i]);
        }
        res[0] = que.peek();
        for (int i = k; i < nums.length; i++) {
            //移动窗口,移除最前面的元素(不一定还在不一定最大值),加入最后,存入res
            que.poll(nums[i - k]);
            que.push(nums[i]);
            res[i-k+1] = que.peek();
        }
        return res;
    }
}

思路:

  1. 暴力遍历O(n × k)
  2. **大顶堆(优先级队列)**存放k个数字,但是问题是这个窗口是移动的,而大顶堆每次只能弹出最大值,我们无法移除其他数值,这样就造成大顶堆维护的不是滑动窗口里面的数值了。所以不能用大顶堆。
  3. 自定义队列:que.pop(滑动窗口中移除元素的数值),que.push(滑动窗口添加元素的数值),然后que.front()就返回我们要的最大值。但已经排序之后的队列 怎么能把窗口要移除的元素(这个元素可不一定是最大值)弹出呢?
  4. 自定义队列只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队列里的元素数值是由大到小的。即单调队列,单调递减或单调递增的队列
  5. push循环条件不能等于, 如[-7,-8,7,5,7,1,6,0], k = 4,等于会移除77时第一个7,只有一个7,导致poll val==peek时弹出之后没有保留的最大值7。所以队列单调递减,但可以有相等值。
  6. 我们用deque作为单调队列的底层数据结构,deque是stack和queue默认的底层实现容器,deque是可以两边扩展的,而且deque里元素并不是严格的连续分布的。
    在这里插入图片描述
  7. 时间复杂度: O(n)
    空间复杂度: O(k)
    nums 中的每个元素最多也就被 push_back 和 pop_back 各一次,没有任何多余操作,O(n)

347.前 K 个高频元素

题目链接:https://leetcode.com/problems/top-k-frequent-elements/

Input: nums = [1,1,1,2,2,3], k = 2
Output: [1,2]

思路:

  1. 要统计元素出现频率: map统计
  2. 对频率排序:快排对整个数组排序 O(nlogn)
    优先级队列(大顶堆,小顶堆)O(nlogk)

缺省情况下priority_queue利用max-heap(大顶堆)完成对元素的排序,这个大顶堆是以vector为表现形式的complete binary tree(完全二叉树)。
堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。大顶堆(堆头是最大元素),小顶堆(堆头是最小元素)。

  1. 找出前K个高频元素

方法一:小顶堆,要统计最大前k个元素,只有小顶堆每次将最小的元素弹出,最后小顶堆里积累的才是前k个最大元素。

class Solution {
    // 小顶堆
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> map = new HashMap<>(); // key为元素值,value为频率
        for (int num: nums) {
            map.put(num, map.getOrDefault(num,0) + 1);
        }

        // 维护小顶堆:出现次数按从队头到队尾的顺序是从小到大排,递增,出现次数最低的在队头
        PriorityQueue<Integer> min_heap = new PriorityQueue<>((a, b) -> map.get(a) - map.get(b));
        for (int num : map.keySet()) {
            if (min_heap.size() < k) { // k个以下,直接加
                min_heap.offer(num);
            } else if (map.get(num) > map.get(min_heap.peek())){ 
            // 如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
            // 大于当前小顶堆最小频率,弹出原来最小的,加入更大的
                min_heap.poll();
                min_heap.offer(num);
            }
        }

        int[] res = new int[k];
        for (int i =0; i < k; i++) {
            res[i] = min_heap.poll();
        }
        return res;
    }
}

时间复杂度: O(nlogk)
空间复杂度: O(n)

方法二:大顶堆,弹出前k个最大的

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

        PriorityQueue<Integer> max_heap = new PriorityQueue<>((a, b)->map.get(b)-map.get(a));
        for (int num: map.keySet()) {
            max_heap.offer(num);
        }

        int[] res = new int[k];
        for (int i = 0; i < k; i++) {
            res[i] = max_heap.poll();
        }
        return res;
    }
}

补充:

✨map.put(num,map.getOrDefault(num, 0) + 1)
①map中含有num的话,就将num对应的value值+1
②map中不含有num的话,num对应的value对应的默认值赋值为0,然后再+1

✨PriorityQueue(优先队列),在概念上,默认为小顶堆,元素单调递增排序。也可通过传入Comparator,重写其中的compare方法自定义排序规则。

1、实现降序排列(大顶堆)
lambda表达式

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

重写compare

PriorityQueue<Integer> queue = new PriorityQueue<>(new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o2 - o1;
    }
});
  1. 实现自定义排序
    示例一、按字符串的第三位进行降序排列
PriorityQueue<String> queue = new PriorityQueue<>(
     (o1, o2) -> o2.charAt(2) - o1.charAt(2)
);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值