zoukankan      html  css  js  c++  java
  • 【每日一题】前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))

  • 相关阅读:
    电脑开不开机 且开且珍惜
    IA32系统级架构总览(二)
    IA32系统级架构总览(一) 实模式和保护模式
    Django 步骤
    【Python】使用Supervisor来管理Python的进程
    python json操作
    term2 配置
    被执行人查询
    Linux下redis的安装
    FTP命令
  • 原文地址:https://www.cnblogs.com/summerday152/p/13626182.html
Copyright © 2011-2022 走看看