java数据结构与算法刷题-----LeetCode347. 前 K 个高频元素

本文介绍了两种方法解决Java中的TopK高频元素问题:法一利用桶排序,时间复杂度O(n),空间复杂度O(n);法二采用小根堆,具有更好的空间效率,适合实战。详细讲解了两种方法的原理和代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

java数据结构与算法刷题目录(剑指Offer、LeetCode、ACM)-----主目录-----持续更新(进不去说明我没写完):https://blog.youkuaiyun.com/grd_java/article/details/123063846

在这里插入图片描述

1. 法一:桶排序

解题思路:时间复杂度O(n),空间复杂度O(n)
  1. 我们先通过hash表,将每个元素的出现频率统计出来
  2. 根据出现频率进行桶排序
  3. 那么越后面的桶的元素,出现次数越多。所以从后向前遍历桶,将桶中的元素依次取出
  4. 题目要求取k个,那就取k个为止,放入结果数组中。
    在这里插入图片描述
代码

在这里插入图片描述

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        // 1. 准备计数排序的数组,也就是hash表,这里用数组来充当hash表,做题时,处理速度要优于使用HashMap,
        //注意实际工作场景中,使用线程安全的HashMap容器,而不用使用数组来作为hash
        int max = Integer.MIN_VALUE;
        int min = Integer.MAX_VALUE;
        for(int num: nums) {
            if(num > max)
                max = num;
            if(num < min)
                min = num;
        }
        //创建可以容纳数组中所有取值的hash表,用下标索引代表nums数组中元素值
        if (max == min) return new int[] {nums[0]};
        int [] count = new int[max - min + 1];
        int bucketLength = 0;//桶排序,桶的数量,我们要对出现频率桶排序,所以,最大的出现频率+1就是桶的数量
        for(int num: nums) {//hash数组下标从0开始,下标0代表nums中的最小值,因此每个元素存储时,要进行-min+1
            count[num - min]++;
            bucketLength = bucketLength<count[num-min]?count[num-min]:bucketLength;//最大出现频率
        }

        // 2. 将计数排序数组转化为 存放频率的桶,对hash表中存储的元素出现频率,进行桶排序
        List<Integer> [] bucket = new ArrayList[bucketLength + 1];//最大出现频率+1为桶的数量
        //进行桶排序
        for(int i = 0; i < count.length; i++) {
            if(count[i] > 0) {
                if(bucket[count[i]] == null) {//每个桶都是一个链表
                    bucket[count[i]] = new ArrayList();
                }
                bucket[count[i]].add(i + min);//存放时,不在存放下标,而是元素值本身
            }
        }
        // 3. 从高频率往低频率遍历(从后往前遍历),构造结果数组
        int [] res = new int[k];
        for(int i = bucket.length - 1, j = 0; j < k && i >= 0; i--) {
            List<Integer> fdata = bucket[i];//获取当前桶中的所有结点
            if(fdata != null) {//如果有结点
                int size = fdata.size();
                //将当前出现频率的结点,依次放入res结果数组中
                // for(int index = size - 1; index >= 0; index--) {
                //     res[j++] = fdata.get(index);
                // }//桶内无论怎么输出,都无所谓
                for(int index = 0; index <= size-1; index++) {
                    res[j++] = fdata.get(index);
                }
            }
        }
        return res;
    }
}

2. 法二:小根堆(更具实战意义)

此方法更推荐实际工作中使用,因为桶排序的不确定因素有很多,很可能出现桶的数量过多,从而浪费大量空间的情况。

解题思路
  1. 和桶排序基本类似。只不过将桶排序,换成了堆排序(小根堆)
  2. 先hash统计出现次数
  3. 通过优先级队列,实现小根堆。保证堆顶永远是最少出现次数。
  4. 先插入k个元素到小根堆。因为题目要求返回k个出现频率最高的
  5. 之后如果还有元素,和堆顶进行比较,如果比堆顶小,就舍弃。否则取出堆顶元素,让当前新元素入堆。
代码

在这里插入图片描述

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        //建立hash映射
        Map<Integer, Integer> occurrences = new HashMap<Integer, Integer>();
        for (int num : nums) {
            occurrences.put(num, occurrences.getOrDefault(num, 0) + 1);
        }

        // int[] 的第一个元素代表数组的值,第二个元素代表了该值出现的次数
        //优先级队列,就是堆。默认是大根堆
        PriorityQueue<int[]> queue = new PriorityQueue<int[]>(new Comparator<int[]>() {//改为小根堆
            public int compare(int[] m, int[] n) {//出现次数谁高,谁优先级就高,会最优先出堆
                //n表示堆顶,m是新插入
                //compare方法,如果返回值 >0 那么m放入n后面
                //如果m更大,m就插入到n的下面,n继续是堆顶,从而实现小根堆
                return m[1] - n[1];//优先级高的先出堆。也就是它们在堆的最下面,最优先出堆
                //n表示堆顶,m是新插入,如果返回值 < 0 m就作为新的堆顶,从而实现大根堆
                //如果m更大,n - m必然小于0, 那么m成为新的堆顶
                //return n[1] - m[1];
            }
        });
        //构建小根堆。堆中元素=k.检查堆顶,和当前想要入堆的出现次数,
        //因为我们只需要k个出现频率最高的,所以已经有k个了,后面想入堆的需要进行判断
        //如果想入堆的出现次数更高,就弹出堆顶,让其入堆。如果没有堆顶大,就舍去想入堆的
        //而如果堆中元素 < k,说明还没有凑够题目要求的k个元素,无脑入堆
        for (Map.Entry<Integer, Integer> entry : occurrences.entrySet()) {
            int num = entry.getKey(), count = entry.getValue();
            if (queue.size() == k) {//如果堆中已经有k个元素
                if (queue.peek()[1] < count) {//看看堆顶是否 < 当前元素
                    queue.poll();//如果小,说明当前元素出现频率更高,那么堆顶不要了,新元素入堆
                    queue.offer(new int[]{num, count});
                }
            } else {//如果堆中没有够k个,无脑入堆
                queue.offer(new int[]{num, count});
            }
        }
        int[] ret = new int[k];//答案数组
        for (int i = 0; i < k; ++i) {
            ret[i] = queue.poll()[0];//依次出堆
        }
        return ret;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ydenergy_殷志鹏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值