代码随想录算法训练营第十三天| 239. 滑动窗口最大值,347.前 K 个高频元素

LeetCode 239. 滑动窗口最大值

暴力解法超时,代码如下:

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        Queue<Integer> queue = new ArrayDeque<>();
        int left = 0;
        int right = k - 1;
        while(right < nums.length){
            int max = nums[left];
            for(int i=left; i<=right; i++){
                if(nums[i] > max){
                    max = nums[i];
                }
            }
            queue.offer(max);
            left++;
            right = left + k - 1;
        }
        int[] results = new int[queue.size()];
        int index = 0;
        while(queue.size() != 0){
            results[index++] = queue.poll();
        }
        return results;
    }
}

看题解文章发现看不明白,于是去听了视频讲解,大致思路:定义一个单调栈,只维护可能成为最大值的元素,队列为单调增的,保证队列的头一定是最大的。当要push一个元素进队列前,先检查当前队列中是否有比该元素小的,若有则将这些元素弹出,由于该队列是单调增的,所以可以从最后一个元素开始比较,知道队列中的元素都比该元素大,则将该元素加入队列中;当要pop一个元素时,只需判断该元素是否等于当前头部的元素,若等于,则将头部元素弹出,若不等于则无需进行操作,因为头部元素之前的队列都已被弹出;当要访问当前队列的最大值时,直接返回头部元素。

该题的关键在于使用deque数据结构,即双端队列 double ended queue,其既可头尾添加元素,又可头尾删除元素,使用起来非常方便。

该题中滑动窗口的移动与队列的pop和push操作十分类似,下次遇到这种题要反应过来的。

代码如下:

class MyQueue {
    Deque<Integer> queue = new ArrayDeque<>();
    void pop(int val){
        if(queue.size() > 0 && val == queue.peek()) {
            queue.poll();
        }
    }
    void push(int val) {
        while(queue.size() > 0 && queue.getLast() < val){
            queue.removeLast();
        }
        queue.offer(val);
    }
    int getMax() {
        return queue.peek();
    }
}

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if(nums.length == 1) return nums;
        MyQueue queue = new MyQueue();
        int[] result = new int[nums.length - k + 1];
        int count = 0;
        for(int i=0; i<k; i++){
            queue.push(nums[i]);
        }
        result[count++] = queue.getMax();
        for(int i=k; i<nums.length; i++){
            queue.pop(nums[i-k]);
            queue.push(nums[i]);
            result[count++] = queue.getMax();
        }
        return result;
    }
}

LeetCode 347.前 K 个高频元素

这道题完全是看题解做出来的,就当是学习笔记。

该题主要涉及三块内容:

1.统计各数出现频率;

2.按照频率进行排序;

3.得到频率前k个数;

对于第一块,由于要同时记录数和频率,所以应马上想到使用Map来做统计。

对于第二块,涉及到我应该学习的新知识,有个数据结构叫PriorityQueue,是一种基于优先级堆的无界优先级队列(百度说的,没明白),它是一个队列,其中每个元素都被赋予了一个优先级。具有较高优先级的元素先被处理,而具有较低优先级的元素后被处理。同等优先级的元素按照它们被添加到队列的顺序进行处理。

什么是堆呢?

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

此时要思考一下,是使用小顶堆呢,还是大顶堆?

若定义一个大小为k的大顶堆,在每次移动更新大顶堆的时候,每次弹出都把最大的元素弹出去了,那么怎么保留下来前K个高频元素呢。而且使用大顶堆就要把所有元素都进行排序,那能不能只排序k个元素呢?

所以我们要用小顶堆,因为要统计最大前k个元素,只有小顶堆每次将最小的元素弹出,最后小顶堆里积累的才是前k个最大元素。

对于第三块,将最后得到的小顶堆依次弹出,最先弹出的出现的次数最少,后面的会依次增加。

代码如下:

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> map = new HashMap<>();
        // 1.统计元素出现频率
        for(int num:nums) {
            map.put(num, map.getOrDefault(num, 0) + 1);
        }
        // 2.对频率排序
        // 使用优先队列PriorityQueue对出现次数进行升序排序(小顶堆)
        PriorityQueue<int[]> pq = new PriorityQueue<>((pair1,pair2)->pair1[1]-pair2[1]);
        for(Map.Entry<Integer,Integer> entry:map.entrySet()) {
            if(pq.size() < k){
                pq.add(new int[]{entry.getKey(), entry.getValue()});
            } else {
                // 3.找出前K个高频元素
                if(entry.getValue() > pq.peek()[1]) {
                    pq.poll();  // 弹出小顶堆的头部,即k个元素中最小的
                    pq.add(new int[]{entry.getKey(), entry.getValue()}); // 将新元素加进来
                }
            }
        }
        int[] result = new int[k];
        for(int i=k-1; i>=0; i--){
            result[i] = pq.poll()[0];
        }
        return result;
    }
}

在Java中,PriorityQueue默认是按照升序排序的。当我们传入一个Lambda表达式作为比较器时,这个比较器决定了队列中元素的排序方式。在这个例子中,Lambda表达式(pair1, pair2) -> pair1[1] - pair2[1]表示,优先队列会根据每个元素的第二个元素进行升序排序。

如果要按照降序排序,我们可以将Lambda表达式修改为(pair1, pair2) -> pair2[1] - pair1[1]。这样,优先队列就会根据第二个元素进行降序排序。

总结

灵魂四问:

1.Java中stack,queue 是容器么?

答:Java中的Stack和Queue是容器,它们是Java集合框架的一部分,

Stack是一种后进先出(LIFO)的数据结构,Java中提供了Stack类来实现这种数据结构。

Queue是一种先进先出(FIFO)的数据结构,Java中提供了Queue接口以及一些实现类,如LinkedList和PriorityQueue,来实现这种数据结构。

这些容器类都是Java集合框架的一部分,该框架是一组用于存储和管理数据的类和接口的集合。除了Stack和Queue之外,Java集合框架还包括其他类型的容器,如List、Set、Map等。

2.Java中stack,queue是如何实现的?

对于Stack类,继承自Vector,它的实现比较简单,底层实际上还是数组,主要是通过一个数组来存储元素。每个栈帧(包括对象和对应的描述符)都存储在一个数组的元素中。

对于Queue接口,Java提供了多种实现方式,如LinkedList、ArrayDeque、PriorityQueue等。这些实现类都是基于链表或数组来实现的。以LinkedList为例,它实现了Queue接口,通过维护一个链表结构来实现元素的存储和取出。当一个元素被入队时,将其添加到链表的末尾;当元素被出队时,从链表头部删除该元素。

3.stack,queue 提供迭代器来遍历空间么?

答:Java中的Stack类和Queue接口都提供了迭代器来遍历其元素。由于Stack和Queue是基于线性数据结构的,因此它们的迭代器是按顺序访问元素的。在遍历过程中,不能对元素进行随机访问,只能按照特定的顺序进行遍历。

对于Stack类,可以通过调用iterator()方法来获取一个迭代器。该迭代器可以遍历栈中的元素,按照它们被压入栈的顺序进行访问。

对于Queue接口,不同的实现类提供的方法可能会有所不同。例如,LinkedList类提供了iterator()方法来获取一个迭代器,而ArrayDeque类提供了iterator(),descendingIterator()方法来获取迭代器和反向迭代器。这些迭代器可以按照特定的顺序遍历队列中的元素。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值