加入收藏 | 设为首页 | 会员中心 | 我要投稿 北几岛 (https://www.beijidao.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

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

发布时间:2021-05-21 07:36:38 所属栏目:大数据 来源: https://www.jb51.cc
导读:347. 前 K 个高频元素 给定一个非空的整数数组,返回其中出现频率前 k 高的元素。 示例 1: 输入: nums = [1,1,2,3],k = 2输出: [1,2] 示例 2: 输入: nums = [1],k = 1输出: [1] 提示: 你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个

347. 前 K 个高频元素

给定一个非空的整数数组,返回其中出现频率前 k 高的元素。

示例 1:

输入: nums = [1,1,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,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,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))

(编辑:北几岛)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读