347. 前 K 个高频元素
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
提示:
- 你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
- 你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。
- 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的。
- 你可以按任意顺序返回答案。
思考:
一般这种求topk的题目,往往都会利用堆进行操作。且一般来说,大顶堆用于前k小的元素,小顶堆用于求前k大的元素,模板如下:
//创建堆 默认是小根堆,即每次堆顶元素是最小的 ,
Queue<Integer> pq = new PriorityQueue<>();
//大根堆的创建方式:Queue<Integer> pq = new PriorityQueue<>((v1, v2) -> v2 - v1);
for(int num : arr){
if(pq.size() < k){
pq.offer(num); // 堆内元素个数小于 k 的时候,无脑插入
}else if(num > pq.peek()) // 以据题意进行改变,需要存前k大的数,那么当前数必须大于堆顶才有机会入队
{
pq.poll();
pq.offer(num);
}
}
在这道题目中,很容易能够想到的思路是:
- 遍历一遍数组,将数字和出现的次数作为key和value存入hash,下面有个很简洁的写法,也是我最近才学习到的:
Map<Integer,Integer> map = new HashMap<>();
for(int num : nums){
map.put(num,map.getOrDefault(num,0)+1);
}
- 建立小根堆,Java中可以使用PriorityQueue实现,且默认是小根堆,并且我们可以定制一个以map中的value作为比较依据的比较器。当然这道题大根堆也是可以的,全部入队,弹出k个最大的即可。
PriorityQueue<Integer> queue = new PriorityQueue<>((x,y)->map.get(x)-map.get(y));
-
遍历map结果集,然后依次根据key取出次数。
-
堆操作比较经典的做法:
- 使用小根堆
- 堆元素小于k,直接插入堆中。
- 堆元素大于等于k,则判断当前的次数是否大于堆顶的元素大小,只有比堆顶大的元素才有资格进入堆中,相应的堆动态地移除最小的一个元素。
- 使用大根堆
- 将元素全部加入堆中,弹出k个最大的即可。
- 使用小根堆
-
将堆中的k个元素依次存入数组即可。
public int[] topKFrequent(int[] nums, int k) {
Map<Integer,Integer> map = new HashMap<>();
int[] arr = new int[k];
for(int num : nums){ //简洁地存储元素出现地次数
map.put(num,map.getOrDefault(num,0)+1);
}
PriorityQueue<Integer> queue = new PriorityQueue<>((x,y)->map.get(x)-map.get(y));
for(int key : map.keySet()){
if(queue.size() < k) queue.offer(key);
else if(map.get(key) > map.get(queue.peek())){
queue.remove();
queue.offer(key);
}
}
int index = 0;
while(!queue.isEmpty()){
arr[index++] = queue.remove();
}
return arr;
}
时间复杂度:(O(Nlogk+N)),N为nums数组的长度,遍历数组将数字出现的次数记录到hash中总共需要进行N次,之后建立堆,每次堆的操作消耗 (logk) 时间,共计(O(Nlogk)),两者相加得到结果。
空间复杂度:(O(N+K)),hash的空间N,堆空间为k。
//大根堆做法
public int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
for (int n : nums){
map.put(n, map.getOrDefault(n, 0) + 1);
}
PriorityQueue<Map.Entry<Integer, Integer>> queue = new PriorityQueue<>((e1, e2) -> e2.getValue() - e1.getValue());
queue.addAll(map.entrySet());
int[] ans = new int[k];
for (int i = 0; i < k && !queue.isEmpty(); ++i){
ans[i] = queue.poll().getKey();
}
return ans;
}
那么这里时间复杂度就是(NlogN)了,空间复杂度(O(2N))。