解析:(小顶堆)
题目要求:给你一个整数数组 nums
和一个整数 k
,请你返回其中出现频率前 k
高的元素。你可以按 任意顺序 返回答案。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
大体思路:
①题目涉及了出现次数,首先想到使用map进行存储次数。
②题目需要找前k个最大值,首先想到使用小顶堆来做
具体步骤:
①定义一个map,key存储数组元素,value存储元素出现的次数。
②定义一个小顶堆,用于寻找k个最大的元素,存放数组{元素,出现次数}。小顶堆里的存储顺序是从队头到队尾是从小到大的。这个堆的排列顺序是按照出现次数进行排序的。
③初始化堆,添加入map中前k个元素。遍历map是使用entrySet()返回含有键值对entry对象的集合,再增强for遍历这个集合。添加{entry.getKey(), entry.getValue()}到小顶堆中。
④向后遍历,每次添加元素前,比较元素是否大于队头存放的int数组的第1个值(也就是堆顶(队头)元素,对应的是堆中的最小值),大于队头元素,则弹出队头元素后再添加{entry.getKey(), entry.getValue()}进入堆。这样可确保堆顶的是大于堆外其他元素的,因为除非堆外元素大于堆顶,否则不可以被添加进来。
注意点:(堆和Map的基础知识)
注意点①:
java中使用优先级队列PriorityQueue实现堆:
从队头到队尾按从小到大排就是最小堆(小顶堆),队头是最小的元素
从队头到队尾按从大到小排就是最大堆(大顶堆),队头是最大的元素
示例:
// 使用优先级队列实现小顶堆,优先级队列里存放了一个一个的int[]数组,并且按照数组的第1位的大小关系,从队头到队尾由小到大进行排序
// lambda表达式i-j是小的排前面
PriorityQueue<int[]> pq = new PriorityQueue<>((i, j) -> i[1] - j[1]);
注意点②:
Map.Entry 键值对 (遍历map数组的方法)
Map.entrySet()会返回一个集合set,这个set集合里每一个元素的类型是Map.Entry<K,V>。
Map.Entry<K,V>表示Map中的一个键值对(一个key-value对),这也是一个类,只是Entry是Map中的静态内部类。
Map.Entry<K,V>类可以通过getKey(), getValue()进行获取键和值
所以可以使用Entry来遍历Map数组:
for (Map.Entry<Integer, Integer> entry : map.entrySet()){
System.out.println(entry.getKey());
System.out.println(entry.getValue())
}
注意点③:
map.getOrDefault(key) 返回map中这个key对应的value,如果没有这个key,返回设定值
这个方法适合用于统计key出现的次数
for (int i : nums) {
// 实现统计nums数组中相同数字出现的次数
map.put(i, map.getOrDefault(i, 0) + 1);
}
注意点④:
new int[]{元素1,元素2 …} 不仅在数组初始化时可以用,也可以直接用
比如:set.add(new int[]{1, 2, 3, 4});
代码:
class Solution {
public int[] topKFrequent(int[] nums, int k) {
HashMap<Integer, Integer> map = new HashMap<>();
// 将数组中的元素,按照(值,出现次数)存入Map
for (int i : nums) {
map.put(i, map.getOrDefault(i, 0) + 1);
}
// 定义优先级队列,存放数组,数组用于存放{值,出现次数},并且按照数组的第1个元素(也就是出现次数)的从队头到队尾按照从小到达进行排序。这样就实现了小顶堆。
PriorityQueue<int[]> pq = new PriorityQueue<>((i, j) -> i[1] - j[1]);
int index = 0;
// 遍历map,使用entrySet(),获取其键值对对象Entry进行遍历
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
// 先存入前k个元素
if (index < k) {
pq.add(new int[]{entry.getKey(), entry.getValue()});
index++;
} else {
// 当输入的元素大于堆顶元素,弹出堆顶元素,存入这个元素
// 因为小顶堆的队头是最小值,每次弹出最小值,加入更大的元素,这样确保堆内最小的元素都大于数组所有其他元素,也就保证了堆是里存放的是前k个最大的元素。
if (pq.peek()[1] < entry.getValue()) {
pq.poll();
pq.add(new int[]{entry.getKey(), entry.getValue()});
}
// 如果当输入的元素小于堆顶元素,不做任何操作,直接略过这个元素
}
}
int[] res = new int[k];
// 最后输出了出现次数由小到大的序列(题目表示可以按任意顺序返回答案)
for (int i = 0; i < k; i++) {
res[i] = pq.poll()[0];
}
return res;
}
}