leetcode-347前k个高频元素

本文介绍了如何解决LeetCode中第347题——找出数组中出现频率前k高的元素。提供了两种解决方案,一种是利用优先队列(最小堆)维护频率,另一种是使用桶排序。两种方法的时间复杂度分别为O(nlogk)和O(n),并详细解析了代码实现过程和复杂度分析。

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

题目:

给定一个非空的整数数组,返回其中出现频率前 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 是数组的大小。

 

思路:这个我直接从leetcode的评论中找到答案,一共是两种,涉及到hash统计出现频数,堆排序和桶排序,先把大神们的代码写在这里。

代码一来自leetcode作者::MisterBooo

代码1思路:

PriorityQueue 一个基于优先级的无界优先级队列。优先级队列的元素按照其自然顺序进行排序,或者根据构造队列时提供的 Comparator 进行排序,具体取决于所使用的构造方法。该队列不允许使用 null 元素也不允许插入不可比较的对象(没有实现Comparable接口的对象)。最小堆是经过排序的完全二叉树,其中任意非终端节点的值均不大于左右节点的值。

复杂度分析:

时间复杂度:O(nlogk), n 表示数组的长度。首先,遍历一遍数组统计元素的频率,这一系列操作的时间复杂度是 O(n);接着,遍历用于存储元素频率的 map,如果元素的频率大于最小堆中顶部的元素,则将顶部的元素删除并将该元素加入堆中,**这里维护堆的数目是 k **,所以这一系列操作的时间复杂度是 O(nlogk)的;因此,总的时间复杂度是 O(nlog⁡k) 。
空间复杂度:O(n),最坏情况下(每个元素都不同),map 需要存储 n 个键值对,优先队列需要存储 k个元素,因此,空间复杂度是 O(n)。

我自己写了代码1,但是出现了好多问题,一个是HashMap中的好几个方法都不怎么熟,不会用;除此之外的使用优先队列构造最小堆也不怎么会写;还有就是我自己写代码的习惯问题了,总是写错单词,这种调试起来非常麻烦,特变难找到,建议写完代码之后还是整体自己先检查一遍,重点看有没有拼错。

peek():用于检索或获取Queue的第一个元素或Queue头部的元素。检索到的元素不会从队列中删除或删除。

containsKey():是否包含某一个key

map.keySet():HashMap中取出所有键的方法。

Map集合中提供了两种取出方式:


              <1>.  返回值类型Set<k>    方法是: keySet()   :返回此映射中包含的键的 Set 视图
                               将map中所有的键存入到Set集合,因为set具备迭代器,所有迭代方式取出所有的键
                               再根据get()方法  ,获取每一个键对应的值
             
              <2>.  返回值类型:Set<Map.Entry<K,V>>方法是:entrySet()
                             取出的是关系,关系中包含key和value,其中Map.Entry<k,V>来表示这种数据类型
                             即:将map集合中的映射关系存入到set集合中,这个关系的数据类型为:Map.Entry

代码1:

class Solution {
        public List<Integer> topKFrequent(int[] nums, int k) {
            // 使用字典,统计每个元素出现的次数,元素为键,元素出现的次数为值
            HashMap<Integer,Integer> map = new HashMap();
            for(int num : nums){
                if (map.containsKey(num)) {
                    map.put(num, map.get(num) + 1);
                }
                else
                {
                    map.put(num, 1);
                }
            }
            // 遍历map,用最小堆保存频率最大的k个元素,默认的情况下就是实现的小顶堆。
            PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>() {
                @Override public int compare(Integer a, Integer b) {
                    return map.get(a) - map.get(b);
                }
            });

            for (Integer key : map.keySet()) {
                if (pq.size() < k) {
                    pq.add(key);
                                   }
                else if (map.get(key) > map.get(pq.peek())) {
                    pq.remove(); pq.add(key);
                }
            }
            // 取出最小堆中的元素
            List<Integer> res = new ArrayList<>();
            while (!pq.isEmpty()) {
                res.add(pq.remove());
            }
            return res;
        }
    }

代码二来自大神CYC2018。思路是使用桶排序。

复杂度分析

    时间复杂度:O(n), n 表示数组的长度。首先,遍历一遍数组统计元素的频率,这一系列操作的时间复杂度是 O(n);桶的数量为 n + 1,所以桶排序的时间复杂度为 O(n);因此,总的时间复杂度是 O(n)。
    空间复杂度:很明显为 O(n)

思路:这个是使用桶排序,首先第一步还是使用HashMap统计出现的频率。然后创建一个桶,将出现次数为i的数字放置在相应的桶中的list里面,最后从后往前进行遍历,知道取出k个数字。

特别注意,方法ketSet()中的第一个字母k是不大写的,今天找了好长时间的错误,要气吐血。。。。。

代码2:

public List<Integer> topKFrequent(int[] nums, int k) {
Map<Integer, Integer> frequencyForNum = new HashMap<>();
for (int num : nums) {  //这部分应该是频率统计
frequencyForNum.put(num, frequencyForNum.getOrDefault(num, 0) + 1);
}
List<Integer>[] buckets = new ArrayList[nums.length + 1];
for (int key : frequencyForNum.keySet()) {
int frequency = frequencyForNum.get(key);
if (buckets[frequency] == null) {
buckets[frequency] = new ArrayList<>();
}
buckets[frequency].add(key);
}
List<Integer> topK = new ArrayList<>();
for (int i = buckets.length - 1; i >= 0 && topK.size() < k; i--) {
if (buckets[i] == null) {
continue;
}
if (buckets[i].size() <= (k - topK.size())) {
topK.addAll(buckets[i]);
} else {
topK.addAll(buckets[i].subList(0, k - topK.size()));
}
}

return topK;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值