LeetCode347. 前 K 个高频元素
题解分析:首先求出所有元素的频次(映射Map),然后利用频次求出前k高的元素(优先队列)。具体来说,问题转化为“在N个元素中选出前M个元素,M<<N”,最朴素的想法是采用排序,但使用快排等高级排序算法,复杂度在NlogN;更好的方法是采用优先队列,复杂度在NlogM,其思路是使用优先队列,维护当前看到的前M个元素,不断将前M大元素中最小的元素进行替换,因此需要使用最小堆。
用Java标准库中的优先队列PriorityQueue,内部默认是一个最小堆。如果想要改变Java标准库中的类相应的比较方式,解决方案是定义一个新的类(比较器,自定义在优先队列中优先级的比较方式),它实现的是Comparator这个接口,在优先队列构造时,让这个比较器作为优先队列的一个参数,比较器定义了在优先队列中如何决定谁的优先级大。
再进一步,不需要专门为PriorityQueue设置一个类,将一个只用一次的类的声明写成匿名类,即传的参数设为一个匿名类,然后把compare的逻辑写出来即可。
更进一步,匿名类具有变量捕获的能力,匿名类中能拿到算法中声明的所有不可改变的变量,利用匿名类改变Java内置类型之间比较的逻辑。
还能进一步化简,从Java8开始,匿名类可以直接使用lamda表达式。
class Solution { public int[] topKFrequent(int[] nums, int k) { /** * 思路:维护优先队列。 * 时间复杂度O(nlogK) 每次堆操作需要O(logK)的时间 * 空间复杂度O(n) */ Map<Integer,Integer> map = new HashMap<>(); for (int num : nums) { map.put(num, map.getOrDefault(num, 0) + 1); } // 求前 k 大,用小根堆,求前 k 小,用大根堆。 PriorityQueue<Integer> minHeap = new PriorityQueue<>((o1, o2) -> map.get(o1) - map.get(o2)); for (int key : map.keySet()) { if (minHeap.size() < k) { minHeap.add(key); }else { if (map.get(minHeap.peek()) < map.get(key)) { minHeap.poll(); minHeap.add(key); } } } int[] res = new int[k]; int index = 0; for (int n : minHeap) { res[index ++] = n; } return res; } }