代码随想录算法训练营第13天

今日任务

● 239. 滑动窗口最大值 ● 347.前 K 个高频元素 ● 总结

链接:https://docs.qq.com/doc/DUHNpa3F4b2dMUWJ3 (opens in a new tab)

239. 滑动窗口最大值

题解想法

时间复杂度:O(n)

主要思想是队列没有必要维护窗口里的所有元素, 只需要维护有可能成为窗口里最大值的元素就可以了, 同时保证队列里的元素数值是由大到小的。

那么这个维护元素单调递减的队列就叫做单调队列, 即单调递减或单调递增的队列。

而且不要以为实现的单调队列就是对窗口里面的数进行排序, 如果排序的话,那和优先级队列又有什么区别了呢。

class MyDequeue {
    // 双向队列,队尾存储最小值,队首存储最大值
    Deque<Integer> queue = new LinkedList<Integer>();
 
    // 窗口滑动,要移出元素时,比较当前要弹出的数值是否等于队尾的数值,如果相等则弹出
    public void poll(int val) {
        if(!queue.isEmpty() && val == queue.peek()) {
            queue.poll();
        }
    }
 
    // 窗口滑动,要加入元素时,从后往前移除比这个元素小的队列元素
    // 保证了:  1.队列元素从头到尾递增;
    //          2.比这个元素资历深但小的元素都被移除(这些元素以后永远不会是窗口最大值了);
    //          3.移出元素时,由于(2.),可以保证弹出队首元素(记为a)后的下一个元素(记为b)一定还在窗口内,否则a加入的时候b就已经被移除了。
    public void offer(int val) {
        while(!queue.isEmpty() && val > queue.peekLast()) {
            queue.pollLast();
        }
        queue.offer(val);
    }
 
    // 队首元素始终为最大值
    public int peek() {
        return queue.peek();
    }
}
 
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if (nums.length == 1) return nums;
        int[] res = new int[nums.length - k + 1];
        int res_num = 0;
        MyDequeue queue = new MyDequeue();
        // 先将前k的元素放入队列
        for (int i = 0; i < k; i++) {
            queue.offer(nums[i]);
        }
        res[res_num++] = queue.peek();
        for(int i = k; i < nums.length; i++) {
            queue.offer(nums[i]);
            queue.poll(nums[i - k]);
            res[res_num++] = queue.peek();
        }
        return res;
    }
}

回顾

  • java Deque声明:Deque<Integer> queue = new LinkedList<>();

  • java Deque类的常用方法:addFirst()、addLast()、removeFirst()、removeLast()、peekFirst()、peekLast()等。add/offer,element/peek,remove/poll中的三个方法均为重复的方法,区别在于前者在操作失败时会抛出异常,后者返回false或null。、

347.前 K 个高频元素

自己想法

用treeset排序

// 自定义类,实现Comparable接口,重写compareTo方法
class MyCount implements Comparable<MyCount>{
    public int key;
    public int value;
    public MyCount(int key, int value) {
        this.key = key;
        this.value = value;
    }
    public int getKey() {return this.key;}
    public int getValue() {return this.value;}
    public int compareTo(MyCount x) {
        // 这里的“等于返回1”,保证了相同value的元素不会被覆盖
        if(x.getValue() >= this.value) return 1;
        else return -1;
    }
}
class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        // hashmap统计元素出现次数
        Map<Integer, Integer> map = new HashMap<>();
        for(int i = 0; i < nums.length; i++) {
            if(map.containsKey(nums[i])) map.put(nums[i], map.get(nums[i]) + 1);
            else map.put(nums[i], 1);
        }
        
        // treeset排序
        Set<MyCount> tree = new TreeSet<>();
        // 此for复杂度O(n)
        for(Map.Entry<Integer, Integer> e : map.entrySet()) {
            // 此步复杂度O(logn)
            tree.add(new MyCount(e.getKey(), e.getValue()));
        }
 
        // 取tree中前k
        int[] ans = new int[k];
        int i = 0;
        for(MyCount e : tree) {
            ans[i++] = e.getKey();
            if(i == k) break;
        }
        return ans;
    }
}

题解想法

/*Comparator接口说明:
 * 返回负数,形参中第一个参数排在前面;返回正数,形参中第二个参数排在前面
 * 对于队列:排在前面意味着往队头靠
 * 对于堆(使用PriorityQueue实现):从队头到队尾按从小到大排就是最小堆(小顶堆),
 *                                从队头到队尾按从大到小排就是最大堆(大顶堆)--->队头元素相当于堆的根节点
 * */
class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        // hashmap统计元素出现次数
        Map<Integer, Integer> map = new HashMap<>();
        for(int i = 0; i < nums.length; i++) {
            if(map.containsKey(nums[i])) map.put(nums[i], map.get(nums[i]) + 1);
            else map.put(nums[i], 1);
        }
        // (优先队列实现的)小堆来找前k大
        // 小堆存储上述map的键值对
        PriorityQueue<int[]> pq = new PriorityQueue<>((e1, e2) -> e1[1] - e2[1]);
        // 此for复杂度O(n)
        for(Map.Entry<Integer, Integer> e:map.entrySet()) {
            // 先放k个
            if(pq.size() < k) pq.offer(new int[]{e.getKey(), e.getValue()});
            else {
                // 若当前根小于e.getValue(),则剔除根,加入e
                // 此步复杂度O(logk)
                if(e.getValue() > pq.peek()[1]) {
                    pq.poll();
                    pq.offer(new int[]{e.getKey(), e.getValue()});
                }
            }
        }
        int[] ans = new int[k];
        for(int i = 0; i < k; i++) ans[i] = pq.poll()[0];
        return ans;
    }
}
 

回顾

  • java PriorityQueue声明:PriorityQueue<int[]> pq = new PriorityQueue<>((e1, e2) -> e1[1] - e2[1]);

  • java HashMap的entrySet()方法返回一个Set集合,Set集合中存储的是键值对对象Map.Entry<>

  • java TreeSet声明:Set<MyCount> tree = new TreeSet<>();

  • java 实现Comparable接口,以及TreeSet的add()方法