随想录训练营Day13|239. 滑动窗口最大值,347.前 K 个高频元素

53 阅读3分钟

随想录训练营Day13|239. 滑动窗口最大值,347.前 K 个高频元素

标签: LeetCode闯关记


1. 239. 滑动窗口最大值

1.1 算法实现

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        ArrayDeque<Integer> deque = new ArrayDeque<>();
        int resLen = nums.length - k + 1;
        int[] res = new  int[resLen];
        int idx = 0;
        //维护deque双向队列,用于存储nums[]数组中的元素下标
        // eg: head---0--1--2--3--tail
        // 达到两个目的,1. 单调递减,保证deque.peek()为最大值; 2. deque中的值的范围在[i - k + 1, i]
        for (int i = 0; i < nums.length; i++) {
            //保证deque中的值的范围在[i - k + 1, i]
            while(!deque.isEmpty() && deque.peek() < i - k + 1){
                deque.pollFirst();
            }
            //单调递减,保证deque.peek()为最大值;so, 添加进来的元素下标所代表的元素必定要比末尾的小,否则弹出末尾元素
            while (!deque.isEmpty() && nums[i] > nums[deque.peekLast()] ){
              deque.pollLast();
            }
            deque.offer(i);//出现问题: 错写成了deque.push(i),offer() 方法将元素添加到队列的尾部,而 push() 方法将元素添加到队列的头部,也就是在队列的最前面插入元素。

            //维护好deque后,当i完成对第一个滑动窗口的遍历之后,每i++,只需将deque.peek()获得,即为当前滑动窗口的maxvalue;
            if(i >= k - 1){
                res[idx] = nums[deque.peek()];
                idx++;
            }
        }
        return res;
    }
}

1.2 题目总结

2. 347.前 K 个高频元素

2.1 算法实现

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        //1.用map来统计出现的元素,和元素出现的频率<k,v>
        Map<Integer,Integer> map = new HashMap<>();
        for (int num :nums) {
            map.put(num, map.getOrDefault(num,0)+1);
        }
        //2.在优先队列中存储二元组(num,cnt),cnt表示元素值num在数组中的出现次数
        //优先队列实现小顶堆
       
        PriorityQueue<int[]> pq = new PriorityQueue<>((pair1,pair2)-> pair1[1]- pair2[1]);
        for(Map.Entry<Integer,Integer> entry: map.entrySet()){
            if(pq.size() < k){ //小顶堆元素个数小于k个时直接相加
                pq.add(new int[]{entry.getKey(),entry.getValue()});
            }else{
                if(entry.getValue() > pq.peek()[1]){
                    pq.poll();
                    pq.add(new int[]{entry.getKey(), entry.getValue()});
                }
            }
        }
        //新建一维数组来存放前k个高频元素,逆序遍历
        int[] topFrequent = new  int[k];
        for (int i = k - 1; i >= 0; i--){//逆序遍历,先弹出的是堆的根,出现的次数少,放在新数组的最后面
            topFrequent[i] = pq.poll()[0];//使用 pq.poll() 方法从优先队列中弹出一个元素(即出现频率最低的元素),然后通过 [0] 来获取该元素的值 num,并将它存储到 topFrequent 数组的下标为 i 的位置中。由于我们是从 k-1 开始逆序遍历的,因此我们可以保证存储到 topFrequent 数组中的元素是按照从高到低的顺序排列的。
        }
        return topFrequent;
    }
}

2.2 题目总结

解题思路: 1.用map来统计出现的元素,和元素出现的频率<k,v> 2.在优先队列中存储二元组(num,cnt),cnt表示元素值num在数组中的出现次数 . 优先队列实现小顶堆 3.新建一维数组来存放前k个高频元素,逆序遍历

思考点:

  1. 使用hashmap的目的?
  2. 为什么不用小顶堆?
  3. 怎么在PriorityQueue中用compare方法实现小顶堆?
  4. 为什么最后是逆序遍历?

Q:什么时候实现的comparator接口? A:在创建 PriorityQueue 对象时,可以传入一个 Comparator 对象来自定义元素之间的比较方式。在本例中,使用了 lambda 表达式实现了一个匿名的 Comparator 对象来按照元素出现的频率从小到大排序,即小顶堆。 Comparator 接口是一个函数式接口,只有一个抽象方法 compare(),用来定义对象之间的排序规则。lambda 表达式可以替代匿名内部类的方式来实现这个接口,简化了代码的书写。这个 lambda 表达式的形式是:

(pair1, pair2) -> pair2[1] - pair1[1]

它的参数是两个 int 数组 pair1 和 pair2,返回值是一个 int,表示 pair2 的第二个元素减去 pair1 的第二个元素。这样,当 PriorityQueue 对元素进行排序时,会根据这个比较结果来确定元素的优先级。因为这个 lambda 表达式的实现是对第二个元素进行比较,所以最终 PriorityQueue 中的元素会按照它们在 map 中出现的次数从大到小排序。


3.今日感悟

看着标准答案打的代码,需要再学习PriorityQueue的实现方法,很多语法都不是很懂,需要再看看. 3.28补3.27