347. 前 K 个高频元素
题目:给你一个整数数组 nums
和一个整数 k
,请你返回其中出现频率前 k
高的元素。你可以按 任意顺序 返回答案。
这道题之前看了好几遍,不太了解的接口太多,一直就放下了,今天终于把它搞定了!
补充知识:
1.getOrDefault()方法的使用
1.getOrDefault(key,default)
作用:如果存在相应的key则返回其对应的value,否则返回给定的默认值。
2.key的值相同,使value的值加一。比如需要统计一个字符串中所含的字母及对应字母的个数。
方法:
(1)定义一个哈希表hash
(2)hash.put(c,hash.getOrDefault(c,0)+1); //若没有就是0,若有就是原有值增一。
3.比较两个哈希表中key对应的value值大小:如果其中一个哈希表中不存在某个key,那么两个hash表如何比较。
方法:
hash1.getOrDefault(c,0) < hash2.getOrDefault(c,0) //如果两个哈希表都有某个key值,那么他们比较的就是其中value的大小,若其中一个哈希表不存在某个key
值,那么比较的就是其中的默认值。
2.Map的一些用法
**keySet()**方法返回值是Map中key值的集合;**entrySet()**的返回值也是返回一个Set集合,此集合的类型为Map.Entry。
Map.Entry是Map声明的一个内部接口,此接口为泛型,定义为Entry<K,V>。它表示Map中的一个实体(一个key-value对)。接口中有getKey(),getValue方法。
3.PriorityQueue优先级队列用法
用法
先说结论,JAVA中默认是小根堆,即小的在堆顶(poll时小的出去)
默认的最小堆写法
PriorityQueue<Integer> queue = new PriorityQueue<Integer>(new Comparator<Integer>(){
@Override
public int compare(Integer o1, Integer o2){
return o1 < o2 ? -1 : 1; // 最小优先队列,直接 return o1.compareTo(o2);
// 最大优先队列,则反过来 return o2.compareTo(o1);
}
});
compare含义
compare(Integer o1, Integer o2)
一定要记住o1是待插元素,o2是最后一个非叶子节点,当compare()返回的值<0时入队,>0时不入队
小根堆:o1若要入队,则o1<o2,同时compare需要返回负的,即o1.compareTo(o2)
大根堆:o1若要入队,则o1>o2,同时compare需要返回负的,即o2.compareTo(o1)
lambda表达式写法
大括号
PriorityQueue<Integer> queue =
new PriorityQueue<Integer>((Integer o1, Integer o2)->{
return o1 < o2 ? -1 : 1;
});
省略大括号
PriorityQueue<Integer> queue =
new PriorityQueue<Integer>((Integer o1, Integer o2)-> o1 - o2);
省略变量类型
PriorityQueue<Integer> queue = new PriorityQueue<Integer>((o1, o2)-> o1 - o2);
具体场景
TopK问题:
一、找海量数据中最大的K个(当前这道题)
构造小根堆,堆顶为最小数,不断入队,当队列数大于K时,将最小的堆顶弹出,最后剩下的就是K个最大数
二、数组中最小的K个
构造最大堆,堆顶为最大数,不断入队,当队列数小于K时,将最大的堆顶弹出,最后剩下的就是K个最小数
思路(来自 代码随想录)
这道题目主要涉及到如下三块内容:
- 要统计元素出现频率
- 对频率排序
- 找出前K个高频元素
首先统计元素出现的频率,这一类的问题可以使用map来进行统计。
然后是对频率进行排序,这里我们可以使用一种 容器适配器就是优先级队列。
什么是优先级队列呢?
其实就是一个披着队列外衣的堆,因为优先级队列对外接口只是从队头取元素,从队尾添加元素,再无其他取元素的方式,看起来就是一个队列。
而且优先级队列内部元素是自动依照元素的权值排列。那么它是如何有序排列的呢?
堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。 如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆。
所以大家经常说的大顶堆(堆头是最大元素),小顶堆(堆头是最小元素),如果懒得自己实现的话,就直接用priority_queue(优先级队列)就可以了,底层实
现都是一样的,从小到大排就是小顶堆,从大到小排就是大顶堆。
本题我们就要使用优先级队列来对部分频率进行排序。
此时要思考一下,是使用小顶堆呢,还是大顶堆?
有的同学一想,题目要求前 K 个高频元素,那么果断用大顶堆啊。
那么问题来了,定义一个大小为k的大顶堆,在每次移动更新大顶堆的时候,每次弹出都把最大的元素弹出去了,那么怎么保留下来前K个高频元素呢。
而且使用大顶堆就要把所有元素都进行排序,那能不能只排序k个元素呢?
所以我们要用小顶堆,因为要统计最大前k个元素,只有小顶堆每次将最小的元素弹出,最后小顶堆里积累的才是前k个最大元素。
题解
class Solution {
public int[] topKFrequent(int[] nums, int k) {
int[] result = new int[k];
HashMap<Integer, Integer> map = new HashMap<>();
for (int num : nums) {
map.put(num, map.getOrDefault(num, 0) + 1);
}
Set<Map.Entry<Integer, Integer>> entries = map.entrySet();
// 根据map的value值正序排,相当于一个小顶堆
PriorityQueue<Map.Entry<Integer, Integer>> queue = new PriorityQueue<>((o1, o2) -> o1.getValue() - o2.getValue());
for (Map.Entry<Integer, Integer> entry : entries) {
queue.offer(entry);
if (queue.size() > k) {
queue.poll();
}
}
for (int i = k - 1; i >= 0; i--) {
result[i] = queue.poll().getKey();
}
return result;
}
}