为什么用小顶堆而不是大顶堆?
如果是大顶堆,每次弹出的元素是堆顶元素,这样会导致频道最高的元素弹出,而不是先弹出频率最小的元素。
本题要解决的问题:
1、统计元素的频率 -->要建立元素与其次数的映射,因此用哈希表,统计元素的频率
2、对频率进行排序 -->小顶堆
3、选择前 K 个频率最大的元素-->如果新元素的频率比堆顶元素(频率最小的元素)大,则弹出堆顶元素,将新元素加入堆中
法一:(推荐)
class Solution {
public int[] topKFrequent(int[] nums, int k) {
//创建哈希表,统计每个元素出现的次数,键是元素,值是元素出现的次数
HashMap<Integer,Integer> map = new HashMap<>();
for(int num:nums){
/*
if(map.containsKey(num))
map.put(num,map.get(num) + 1);
else
map.put(num,1);
*/
map.put(num,map.getOrDefault(num,0)+1);
}
//在优先队列中存储二元组(num,cnt),num表示元素值,cnt表示出现频率
PriorityQueue<int[]> pq = new PriorityQueue<int[]>(new Comparator<int[]>(){
public int compare(int[] a, int[] b){
return a[1] - b[1];
}
});
for(Map.Entry<Integer,Integer> entry:map.entrySet()){ //小顶堆只需要维持 k 个元素有序
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型
int [] ans = new int[k];
for(int i = k-1; i >= 0;i--)
ans[i] = pq.poll()[0];
return ans;
}
}
法二:
class Solution {
public int[] topKFrequent(int[] nums, int k) {
//创建哈希表,统计每个元素出现的次数,键是元素,值是元素出现的次数
HashMap<Integer,Integer> map = new HashMap<>();
for(int num:nums){
/*
if(map.containsKey(num))
map.put(num,map.get(num) + 1);
else
map.put(num,1);
*/
map.put(num,map.getOrDefault(num,0)+1);
}
//遍历map,用最小堆保存频率最大的 K 个元素
PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>(){
public int compare(Integer a,Integer b){
return map.get(a) - map.get(b);
}
});
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);
}
}
//取出小顶堆中的k个元素,注意题目要返回的是int型
int [] ans = new int[k];
for(int i = k-1; i >= 0;i--)
ans[i] = pq.remove();
return ans;
}
}