【每日一题】前k个高频元素

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);
    }
}

在这道题目中,很容易能够想到的思路是:

  1. 遍历一遍数组,将数字和出现的次数作为key和value存入hash,下面有个很简洁的写法,也是我最近才学习到的:
Map<Integer,Integer> map = new HashMap<>();
for(int num : nums){
    map.put(num,map.getOrDefault(num,0)+1);
}
  1. 建立小根堆,Java中可以使用PriorityQueue实现,且默认是小根堆,并且我们可以定制一个以map中的value作为比较依据的比较器。当然这道题大根堆也是可以的,全部入队,弹出k个最大的即可。
PriorityQueue<Integer> queue = new PriorityQueue<>((x,y)->map.get(x)-map.get(y));
  1. 遍历map结果集,然后依次根据key取出次数

  2. 堆操作比较经典的做法:

    1. 使用小根堆
      • 堆元素小于k,直接插入堆中。
      • 堆元素大于等于k,则判断当前的次数是否大于堆顶的元素大小,只有比堆顶大的元素才有资格进入堆中,相应的堆动态地移除最小的一个元素。
    2. 使用大根堆
      • 将元素全部加入堆中,弹出k个最大的即可。
  3. 将堆中的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)\)

posted @ 2020-09-07 12:40  天乔巴夏丶  阅读(228)  评论(0编辑  收藏  举报