题解分析(见课程8-7和8-8):首先求出所有元素的频次(映射Map),然后利用频次求出前k高的元素(优先队列)。具体来说,问题转化为“在N个元素中选出前M个元素,M<<N”,最朴素的想法是采用排序,但使用快排等高级排序算法,复杂度在NlogN;更好的方法是采用优先队列,复杂度在NlogM,其思路是使用优先队列,维护当前看到的前M个元素,不断将前M大元素中最小的元素进行替换,因此需要使用最小堆。那么最小堆如何实现呢?
——对最大堆底层实现进行修改,即最大堆核心逻辑比较时符号需要改变。
——仍然采用最大堆,不一定值越大优先级越高,自己定义元素的值越小,优先级越高即可。 √
队首的元素就是优先队列优先级最高的元素,因此,在自定义优先级下,频次最低的元素优先级最高。如果新遍历的key 的频次比当前前k高频次中频次最小的元素要搞,那么就将有限队列中队首的元素出队,然后进队一个新的元素。该算法的复杂度是NlogK。这是利用自己定义的优先队列,内部是一个最大堆。
用Java标准库中的优先队列PriorityQueue,内部默认是一个最小堆。如果想要改变Java标准库中的类相应的比较方式,解决方案是定义一个新的类(比较器,自定义在优先队列中优先级的比较方式),它实现的是Comparator这个接口,在优先队列构造时,让这个比较器作为优先队列的一个参数,比较器定义了在优先队列中如何决定谁的优先级大。
再进一步,不需要专门为PriorityQueue设置一个类,将一个只用一次的类的声明写成匿名类,即传的参数设为一个匿名类,然后把compare的逻辑写出来即可。
更进一步,匿名类具有变量捕获的能力,匿名类中能拿到算法中声明的所有不可改变的变量,利用匿名类改变Java内置类型之间比较的逻辑。
还能进一步化简,从Java8开始,匿名类可以直接使用lamda表达式。
import java.util.LinkedList; import java.util.TreeMap; import java.util.PriorityQueue; import java.util.Comparator; class Solution { public int[] topKFrequent(int[] nums, int k) { TreeMap<Integer, Integer> map = new TreeMap<>(); for(int num: nums){ if(map.containsKey(num)){ map.put(num, map.get(num) + 1); }else{ map.put(num, 1); } } PriorityQueue<Integer> pq = new PriorityQueue<>( (a,b) -> map.get(a) - map.get(b) // Java8下,匿名类直接使用lamda表达式 ); for(int key: map.keySet()){ if(pq.size()<k){ pq.add(key); }else if (map.get(key) > map.get(pq.peek())){ pq.remove(); pq.add(key); } } LinkedList<Integer> res = new LinkedList<>(); while(!pq.isEmpty()){ res.add(pq.remove()); } int[] arr= new int[res.size()]; for(int i = 0; i<res.size(); i++){ arr[i] = res.get(i); // LinkedList转换为原始int数组 } return arr; } }