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

239. 滑动窗口最大值

  • 刷题icon-default.png?t=N7T8https://leetcode.cn/problems/sliding-window-maximum/description/
  • 文章讲解icon-default.png?t=N7T8https://programmercarl.com/0239.%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E6%9C%80%E5%A4%A7%E5%80%BC.html#%E7%AE%97%E6%B3%95%E5%85%AC%E5%BC%80%E8%AF%BE
  • 视频讲解icon-default.png?t=N7T8https://www.bilibili.com/video/BV1XS4y1p7qj/?vd_source=af4853e80f89e28094a5fe1e220d9062
  • 一刷理解思路:

        借助自定义数据结构单调队列,理解思路,动图如下:

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

  • 题解(自定义数据结构单调队列):
class MyDeque {
    //自定义数据结构实现
    Deque deque = new LinkedList<>();
    //弹出元素
    //若当前要弹出的数值等于队列出口数值,则弹出
    void poll(int val){
        if(!deque.isEmpty() && val == deque.peek()){
            deque.poll();
        }
    }
    //添加元素
    //若要添加的元素大于入口处元素,就将入口处元素弹出,再添加
    //从而保证队列内元素递减
    void add(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) {
        if(nums.length == 1){
            return nums;
        }
        int len = nums.length - k + 1;
        //存放结果元素的数组
        int[] result = new int[len];
        int num = 0;
        // 使用自定义数据结构
        MyDeque mydeque = new MyDeque();
        //将前k的元素放入队列
        for(int i = 0;i < k;i ++){
            mydeque.add(nums[i]);
        }
        result[num++] = mydeque.peek();
        for(int i = k;i < nums.length;i++){
            //滑动窗口移除最前面的元素,移除时判断该元素是否可以放入队列
            mydeque.poll(nums[i-k]);
            //滑动窗口加入最后面的元素
            mydeque.add(nums[i]);
            //记录对应的最大值
            result[num++] = mydeque.peek();
        }
        return result;
    }
}

347.前 K 个高频元素

  • 刷题icon-default.png?t=N7T8https://leetcode.cn/problems/top-k-frequent-elements/description/
  • 文章讲解icon-default.png?t=N7T8https://programmercarl.com/0347.%E5%89%8DK%E4%B8%AA%E9%AB%98%E9%A2%91%E5%85%83%E7%B4%A0.html
  • 视频讲解icon-default.png?t=N7T8https://www.bilibili.com/video/BV1Xg41167Lz/?vd_source=af4853e80f89e28094a5fe1e220d9062
  • Java基础储备:

        1、ArrayDeque与Deque:

        ArrayDeque是Deque接口的一个实现。Deque代表双端队列(double-ended queue),它是一种队列,允许在队列的两端进行插入和删除操作。Deque接口定义了双端队列的行为,而ArrayDeque是Deque接口的一个具体实现,它使用数组作为内部数据结构来实现双端队列的功能。

        因此,ArrayDeque是Deque接口的一种实现方式,它提供了一种使用数组实现双端队列的方式。Deque接口还可以有其他的实现方式,比如LinkedList等。在Java中,我们可以使用Deque接口来表示双端队列的抽象概念,而具体使用哪种实现方式则可以根据具体的需求来选择。

        2、大顶堆与小顶堆的适配应用场景分析:

        可以在O(nlogk)的时间复杂度内找到前k个大的元素,而不需要对整个集合进行排序。

大顶堆和小顶堆是一种特殊的二叉堆数据结构,它们可以快速找到最大或最小的元素,并且在插入和删除元素时具有较好的性能表现。

        在一个数据量庞大的集合中,如果需要找到前k个大的元素,可以使用大顶堆来实现。首先,可以维护一个大小为k的大顶堆,然后遍历集合中的元素,将每个元素与堆顶元素进行比较。如果当前元素大于堆顶元素,则将堆顶元素替换为当前元素,并进行堆调整,以保持堆的性质。最终,堆中的元素即为前k个大的元素。

        类似地,如果需要找到前k个小的元素,可以使用小顶堆来实现。通过这种方法,可以在较短的时间内找到前k个大或小的元素,而不需要对整个集合进行排序,从而提高了算法的效率。

        3、 关于map.getOrDefault(num, 0);

map.put(num, map.getOrDefault(num, 0) + 1);

         这行代码是将num作为键,然后使用getOrDefault方法来获取num对应的值,如果num不存在则返回默认值0。然后将获取到的值加1,最后将结果放入map中,即将num对应的值加1并更新到map中。

   getOrDefault方法是Map接口中的一个方法,它的作用是获取指定键对应的值,如果键不存在,则返回一个默认值。这个方法接收两个参数,第一个参数是要获取值的键,第二个参数是默认值。如果指定的键存在,则返回与该键关联的值,如果键不存在,则返回提供的默认值。

        4、关于以Map映射关系为元素的Set的应用:

Set> entries = map.entrySet();

         上述代码是将map中所有的键值对(key-value)映射关系转化为一个Set集合,这个Set集合的元素类型是Map.Entry。 Map.Entry表示一个映射项(键值对),其中Integer表示键的类型,第二个Integer表示值的类型。

        "Entry" 是指 Map 接口中的一个内部接口,它表示一个键值对(key-value pair)。在这里,"Map.Entry" 表示一个具有整数类型键和整数类型值的键值对。

        entrySet() 是 Map 接口中的一个方法,它返回包含映射关系(键值对)的 Set 视图。这个 Set 中的每个元素都是一个 Map.Entry 对象,每个 Map.Entry 对象包含一个键和对应的值。通过调用 entrySet() 方法,可以获取到 Map 中所有的键值对,并以 Set 的形式返回。

        5、关于Java中的优先队列:

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

         这行代码的意思是创建了一个优先队列(PriorityQueue)对象,该队列中的元素是 Map.Entry 类型。同时,通过传入一个比较器(Comparator)来指定优先队列中元素的排序规则。在这里的比较器中,使用 o2.getValue() - o1.getValue() 来定义元素的排序规则,表示按照值的大小降序排列。

        这行代码中传入了一个Lambda表达式作为比较器,Lambda表达式定义了如何对队列中的元素进行比较和排序。在这里,(o1, o2) -> o2.getValue() - o1.getValue() 表示比较器的逻辑,其中 o1 和 o2 分别表示优先队列中的两个元素。这个比较器的逻辑是通过比较两个 Map.Entry 对象的值来确定它们的顺序。具体来说,o2.getValue() - o1.getValue() 表示按照值的大小进行降序排列,也就是说,值较大的元素会被放在队列的前面。这样,当优先队列进行弹出操作时,会先弹出值较大的元素。

        而且,在Java中,优先队列(PriorityQueue)默认是小顶堆(Min Heap),也就是说,队列头部的元素是队列中最小的元素。因此,为了实现大顶堆(Max Heap),我们需要传入一个自定义的比较器来改变默认的排序规则。在这个例子中,通过传入 (o1, o2) -> o2.getValue() - o1.getValue() 这个Lambda表达式作为比较器,我们改变了默认的排序规则,使得值较大的元素会被放在队列的前面,从而实现了大顶堆的效果。这样,当优先队列进行弹出操作时,会先弹出值较大的元素。

        6、关于Lambda表达式:

        Lambda表达式是Java 8引入的一个重要特性,它提供了一种简洁的语法来表示匿名函数。Lambda表达式可以被视为一种匿名函数,它可以像普通方法一样传递给其他方法,也可以作为函数式接口的实例使用。

        Lambda表达式一般语法: 

(parameters) -> expression
或者
(parameters) -> { statements; }

         其中,parameters表示参数列表,可以为空或非空;箭头 -> 将参数列表和Lambda表达式的主体分隔开;expression或{ statements; }表示Lambda表达式的主体,可以是一个表达式或一段代码块。

        Lambda表达式的主要特点包括:

  1. 简洁性:Lambda表达式可以大大减少冗余代码,使代码更加简洁易读。
  2. 传递行为:Lambda表达式可以作为参数传递给方法,从而实现更灵活的行为传递。
  3. 并行处理:Lambda表达式可以与Stream API一起使用,实现并行处理数据的功能。

        Lambda表达式在Java中被广泛应用于函数式接口、集合操作、并行处理等方面,极大地丰富了Java的编程语言特性。

        7、 offer()函数是用于向队列中插入元素的方法,它在队列满了时(对于有界队列)会返回false,否则会插入元素并返回true。

  • 题解(借助大顶堆实现,语法基础也是关键):

        即,将元素(key)及其出现次数(value)利用HashMap做一个存储,然后将每个元素及其出现次数作为映射关系存储在entrySet,最后利用选择器构建大顶堆,将在Set中存储的映射关系加入优先队列(大顶堆),然后控制弹出k个键值,即弹出了最大的k个元素的键值(key)。

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        //利用大顶堆实现
        int[] result = new int[k];
        HashMap map = new HashMap<>();
        for(int num : nums){
            map.put(num,map.getOrDefault(num, 0) + 1);
        }
        Set> entries = map.entrySet();
        //通过选择器构建大顶堆
        PriorityQueue> queue = new PriorityQueue<>((o1, o2) -> o2.getValue() - o1.getValue());
        for(Map.Entry entry : entries){
            queue.offer(entry);
        }
        //取前k个放入result
        for(int i = k - 1; i >= 0; i--){
            result[i] = queue.poll().getKey();
        }
        return result;
    }
}

你可能感兴趣的:(数据结构,java,算法,leetcode,开发语言)