【代码训练营】day12 | 239. 滑动窗口最大值 & 347.前 K 个高频元素

80 阅读3分钟

所用代码 java

滑动窗口最大值 LeetCode 239

题目链接:滑动窗口最大值 LeetCode 239 - 困难

思路

无。


试了一下暴力,会超时

public int[] maxSlidingWindow(int[] nums, int k) {
    // 新数组长度为nums.length - k + 1
    int[] res = new int[nums.length - k + 1];
    int temp = Integer.MIN_VALUE;
    for (int i = 0; i < nums.length - k + 1; i++) {
        temp = Integer.MIN_VALUE;
        for (int j = i; j < i + k; j++) {
            if (nums[j] > temp){
                temp = nums[j];
            }
        }
        res[i] = temp;
    }
    
    return res;
}

正确做法:单调队列 自定义的一个队列,实现我们需要的pop 、push 和getMaxValue

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if (nums.length == 1) {
            return nums;
        }
        int n = 0;
        MyQueue myQueue = new MyQueue();
        int[] res = new int[nums.length - k + 1];
​
        // 先将前k个元素放入队列,得到第一个最大的数
        for (int i = 0; i < k; i++) {
            myQueue.push(nums[i]);
        }
​
        res[n++] = myQueue.getMaxValue();
​
        // 从k后面添加窗口元素,添加一个移动一次窗口
        for (int i = k; i < nums.length; i++) {
            // 添加之前先移除窗口的第一个值 (1 3 -1 把1移除,实际没有1 )
            myQueue.pop(nums[i-k]);
            // 添加第k+1个元素
            myQueue.push(nums[i]);
            // 获取最大值
            res[n++] = myQueue.getMaxValue();
        }
​
        return res;
    }
​
}
​
// 自定义一个单调队列
class MyQueue{
    Deque<Integer> deque = new ArrayDeque<>();
    // 如果队列的第一个元素等于队头的最大值就弹出
    public void pop(int val){
        if (!deque.isEmpty() && val == deque.peekFirst()){
            deque.pollFirst();
        }
    }
​
    // 从队列尾部加入一个值,该值保证队列从后往前都比它小
    public void push(int val){
        while (!deque.isEmpty() && val > deque.peekLast()){
            deque.pollLast();
        }
        // deque为空或是前面都比val小了直接在后面加入
        deque.offerLast(val);
    }
​
    // 获取队列最大的值,即队列的第一个元素
    public int getMaxValue(){
        return deque.peekFirst();
    }
}

总结

若这种题实在没办法就暴力得一点点分。

单调队列这种方法不难,难的是如何想到用这种方法,并把它实现出来

力扣官方有优先队列的解法,感觉还是很不错:

public int[] maxSlidingWindow(int[] nums, int k) {
    // 使用优先队列
    // int数组两种值,第一个是num,第二个是索引
    PriorityQueue<int[]> pq = new PriorityQueue<>(new Comparator<int[]>() {
        @Override
        public int compare(int[] o1, int[] o2) {
            // 维护大顶堆, 数值不相等第一个数就是最大的,相等就是索引较大的
            return o1[0] != o2[0] ? o2[0] - o1[0] : o2[1] - o1[1];
        }
    });

    //先存k个数进入堆
    for (int i = 0; i < k; i++) {
        pq.offer(new int[]{nums[i], i});
    }
    int n = nums.length;
    int index = 0;
    int[] res = new int[n - k + 1];
    // 赋值第1个数
    res[index++] = pq.peek()[0];

    // 存k到n-k+1个数
    for (int i = k; i < n; i++) {
        // 1、存第k+1个数
        pq.offer(new int[]{nums[i], i});
        // 2、判断堆顶元素的索引是不是已被移除
        // 也就是窗口移动后,该数应被删除
        while (pq.peek()[1] <= i - k){
            pq.poll();
        }
        // 3、堆顶的最大值存入数组
        res[index++] = pq.peek()[0];
    }

    return res;

}

前K个高频元素 LeetCode 347

题目链接:前 K 个高频元素 LodeCode 347 - 中等

思路

用map存每个数据key为数字,value为出现的次数。

代码随想录里面提出我们可以用大顶堆和小顶堆来解决求前k个高频或是低频的操作,也就是优先队列操作(PriorityQueue)这个确实不会,学习了。

难点1: 实现小顶堆方法 (Comparator接口 => o1 - o2 是从小到大排序)

PriorityQueue<Integer> heap=new PriorityQueue<Integer>(new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            // o2-o1 为大顶堆
            return o1-o2;
        }
    });
​
lamda表达式  小顶堆
PriorityQueue<Integer> heap=new PriorityQueue<Integer>( (num1,num2) -> return num1-num2)   

难点2: 遍历map方式

for(Map.Entry<Integer, Integer> entry : map.entrySet()){
    //得到key和value
    entry.getKey()
    entry.getValue()
}

小顶堆实现: 维护k个有序元素

public int[] topKFrequent(int[] nums, int k) {
    // 每个map里面的值为 元素=>出现的次数
    Map<Integer,Integer> map = new HashMap<>();
    for (int i = 0; i < nums.length; i++) {
        //map.getOrDefault(k,default),取key的value,没有就默认值default
        map.put(nums[i],map.getOrDefault(nums[i],0) + 1);
    }
​
    // 创建小顶堆,堆中的值为int[]数组
    // 第一个为nums出现元素,第二个值为该元素出现的次数
    PriorityQueue<int[]> heap = new PriorityQueue<>(new Comparator<int[]>() {
        @Override
        public int compare(int[] o1, int[] o2) {
            // 比较的是次数,所以是数组的第二个数
            return o1[1]-o2[1];
        }
    });
​
    // 小顶堆需维护k个元素有序
    for (Map.Entry<Integer,Integer> entry: map.entrySet()){
        // kv[0]为元素,kv[1]为该元素出现的次数,共两个值
        int[] kv = new int[]{entry.getKey(),entry.getValue()};
        // 小顶堆个数小于k个时直接添加
        if (heap.size() < k){
            heap.add(kv);
        }else {
            // 若map里面存的下一个数大于堆顶的元素,就弹出堆顶元素并加入新元素
            if (entry.getValue() > heap.peek()[1]){
                // 弹出顶堆,即最小的那个数,剩下就是最大的数
                heap.poll();
                heap.add(kv);
            }
        }
    }
​
    int[] res = new int[k];
    // 以此弹出小顶堆,注意是从小到大弹出的
    // 从大到小的话就逆向赋值
    for (int i = k - 1;i >= 0;i--){
        // 需要的是数组的第一个值
        res[i] = heap.poll()[0];
    }
​
    return res;
}

大顶堆实现:取前k个元素,相当于维护了所有元素,耗时更慢

public int[] topKFrequent(int[] nums, int k) {
    // 每个map里面的值为 元素=>出现的次数
    Map<Integer,Integer> map = new HashMap<>();
    for (int i = 0; i < nums.length; i++) {
        //map.getOrDefault(k,default),取key的value,没有就默认值default
        map.put(nums[i],map.getOrDefault(nums[i],0) + 1);
    }
​
    // 创建大顶堆,堆种的值为int[]数组
    // 第一个为nums出现元素,第二个值为该元素出现的次数
    PriorityQueue<int[]> heap = new PriorityQueue<>(new Comparator<int[]>() {
        @Override
        public int compare(int[] o1, int[] o2) {
            // 比较的是次数,所以是数组的第二个数
            return o2[1]-o1[1];
        }
    });
​
    // 大顶堆需维护所有有序元素
    for (Map.Entry<Integer,Integer> entry: map.entrySet()){
        // kv[0]为元素,kv[1]为该元素出现的次数,共两个值
        int[] kv = new int[]{entry.getKey(),entry.getValue()};
        // 所有元素加入大顶堆
        heap.add(kv);
    }
​
    int[] res = new int[k];
    // 直接取大顶堆前k个值
    for (int i = 0;i < k;i++){
        // 需要的是数组的第一个值
        res[i] = heap.poll()[0];
    }
​
    return res;
}

总结

了解了优先队列,也就是堆的使用方法,第二就是知道了map的遍历方法。但是还需要多多熟悉这些方法,才能运用自如。