leetcode hot 100 - 347. 前 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 个高频元素的集合是唯一的。
你可以按任意顺序返回答案。

思路一:HashMap 计数 + 排序

使用hashmap对每个元素进行出现次数统计

对hashmap根据value进行排序,排序前要先把hashmap中的所有entry都存入到一个list中,只有在list中才可以使用Collections.sort()进行排序

保存前k个元素到数组

 1 class Solution {
 2     public int[] topKFrequent(int[] nums, int k) {
 3         // 使用hashmap对每个元素进行出现次数统计
 4         HashMap<Integer, Integer> map = new HashMap<>();
 5         for(int num : nums){
 6             if(map.containsKey(num)){
 7                 map.put(num, map.get(num)+1);
 8             }else{
 9                 map.put(num, 1);
10             }
11         }
12 
13         // 对hashmap根据value进行排序
14         // 先把hashmap中的所有entry都存入到一个list中,只有在list中才可以使用Collections.sort()进行排序
15         List<Map.Entry<Integer, Integer>> list = new ArrayList<>();
16         list.addAll(map.entrySet());
17 
18         Collections.sort(list, new Comparator<Map.Entry<Integer,Integer>>(){
19             public int compare(Map.Entry<Integer,Integer> o1, Map.Entry<Integer,Integer> o2){
20                 return o2.getValue() - o1.getValue();
21             }
22         });
23 
24         // 保存前k个元素到数组
25         int[] res = new int[k];
26         int i = 0;
27         for(Map.Entry<Integer,Integer> en : list){
28             res[i++] = en.getKey();
29             if(i >= k){
30                 break;
31             }
32         }
33         return res;
34     }
35 }

leetcode 执行用时:18 ms > 45.17%, 内存消耗:41.1 MB > 93.84%

复杂度分析:

时间复杂度:O(nlogn), 使用Hashmap进行计数的复杂度为O(n), 随后对hashmap进行排序,虽然其中的键值对可能没有n那么多,但是在最坏情况下键值对的个数为n, 所以排序的时间复杂度为O(nlogn)

空间复杂度:O(2n); 需要一个hashMap存储每个值出现的次数,键值对最大为O(n), 随后将所有健值对存入一个临时 list, 也花费了O(n)的空间,所以整体空间复杂度为O(2n)

思路二:HashMap计数 + 堆

这次仍然是先使用HashMap进行计数,然后维持一个大小为k的最小堆,最后取出堆中所有元素存入一个数组

 1 class Solution {
 2     public int[] topKFrequent(int[] nums, int k) {
 3         // 使用hashmap对每个元素进行出现次数统计
 4         HashMap<Integer, Integer> map = new HashMap<>();
 5         for(int num : nums){
 6             if(map.containsKey(num)){
 7                 map.put(num, map.get(num)+1);
 8             }else{
 9                 map.put(num, 1);
10             }
11         }
12 
13         // 然后维持一个大小为k的最小堆
14         PriorityQueue<Integer> queue = new PriorityQueue<>((a, b) -> map.get(a) - map.get(b));
15         for(Integer key : map.keySet()){
16             queue.offer(key);
17             if(queue.size() > k){
18                 queue.poll();
19             }
20         }
21 
22         // 最后取出堆中所有元素存入一个数组
23         int[] res = new int[k];
24         for(int i = 0; i < k; i++){
25             res[i] = queue.poll();
26         }
27         return res;
28     }
29 }

leetcode 执行用时:18 ms > 45.17%, 内存消耗:41.2 MB > 90.21%

复杂度分析:

时间复杂度: O(nlogk)。hashMap计数的时间是O(n), 后面维持一个大小为k的堆的时间花费为O(nlogk), 所以整体时间复杂度为O(nlogk)。

空间复杂度:O(n+k)。需要一个最大为O(n)的map 和一个 O(k)的堆,所以时间复杂度O(n+k)。

思路二:HashMap计数 + 桶排序

先使用hashmap进行计数,然后创建一个大小为 (n + 1) 的列表 list(之所以是n+1, 因为如果所有元素都是同一个元素的话,那这个元素的出现次数就是n, 那下标就是n, 所以需要一个大小为n+1的列表,这个列表的每个元素被当成是一个桶,每个桶是一个小列表,hashmap的值作为下标,键作为桶里的值,这样这个 list 列表中就保存了每个出现次数的所有元素。最后从大小遍历这个list, 把不为空的子列表元素都加入到数组中

 1 class Solution {
 2     public int[] topKFrequent(int[] nums, int k) {
 3         // 使用hashmap对每个元素进行出现次数统计
 4         HashMap<Integer, Integer> map = new HashMap<>();
 5         for(int num : nums){
 6             if(map.containsKey(num)){
 7                 map.put(num, map.get(num)+1);
 8             }else{
 9                 map.put(num, 1);
10             }
11         }
12 
13         // 然后创建一个大小为 (n + 1) 的列表 list
14         // 这个列表的每个元素被当成是一个桶,每个桶是一个小列表,hashmap的值作为下标,键作为桶里的值,
15         // 这样这个 list 列表中就保存了每个出现次数的所有元素。
16         List<Integer>[] list = new List[nums.length+1];
17         for(Map.Entry<Integer, Integer> en : map.entrySet()){
18             if(list[en.getValue()]== null){
19                 list[en.getValue()] = new ArrayList<Integer>();
20             }
21             list[en.getValue()].add(en.getKey());
22         }
23 
24         // 最后从大小遍历这个list, 把不为空的子列表元素都加入到数组中
25 
26         ArrayList<Integer> res = new ArrayList<>();
27         for(int i = nums.length; i >= 0 && res.size() < k; i--){
28             if(list[i] != null){
29                 res.addAll(list[i]);
30             }
31         }
32         int[] arr = new int[k];
33         for(int i = 0; i < k; i++){
34             arr[i] = res.get(i);
35         }
36         return arr;
37     }
38 }
leetcode 执行用时:14 ms > 92.28%, 内存消耗:41.3 MB > 72.55%

复杂度分析:

时间复杂度:O(n). 三个for循环,所以时间复杂度为O(n)
空间复杂度:需要一个大小为O(n)的hashmap, 一个大小为O(n+1)的list,数组,和一个大小为O(k)的结果列表,所以时间复杂度为O(2n+k+1), 即O(n)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 好的,我来用中文回复这个链接:https://leetcode-cn.com/tag/dynamic-programming/ 这个链接是 LeetCode 上关于动态规划的题目集合。动态规划是一种常用的算法思想,可以用来解决很多实际问题,比如最长公共子序列、背包问题、最短路径等等。在 LeetCode 上,动态规划也是一个非常重要的题型,很多题目都需要用到动态规划的思想来解决。 这个链接里包含了很多关于动态规划的题目,按照难度从简单到困难排列。每个题目都有详细的题目描述、输入输出样例、题目解析和代码实现等内容,非常适合想要学习动态规划算法的人来练习和提自己的能力。 总之,这个链接是一个非常好的学习动态规划算法的资源,建议大家多多利用。 ### 回答2: 动态规划是一种算法思想,通常用于优化具有重叠子问题和最优子结构性质的问题。由于其成熟的数学理论和强大的实用效果,动态规划在计算机科学、数学、经济学、管理学等领域均有重要应用。 在计算机科学领域,动态规划常用于解决最优化问题,如背包问题、图像处理、语音识别、自然语言处理等。同时,在计算机网络和分布式系统中,动态规划也广泛应用于各种优化算法中,如链路优化、路由算法、网络流量控制等。 对于算法领域的程序员而言,动态规划是一种必要的技能和知识点。在LeetCode这样的程序员平台上,题目分类和标签设置十分细致和方便,方便程序员查找并深入学习不同类型的算法LeetCode的动态规划标签下的题目涵盖了各种难度级别和场景的问题。从简单的斐波那契数列、迷宫问题到可以用于实际应用的背包问题、最长公共子序列等,难度不断递进且话题丰富,有助于开发人员掌握动态规划的实际应用技能和抽象思维模式。 因此,深入LeetCode动态规划分类下的题目学习和练习,对于程序员的职业发展和技能提升有着重要的意义。 ### 回答3: 动态规划是一种常见的算法思想,它通过将问题拆分成子问题的方式进行求解。在LeetCode中,动态规划标签涵盖了众多经典和优美的算法问题,例如斐波那契数列、矩阵链乘法、背包问题等。 动态规划的核心思想是“记忆化搜索”,即将中间状态保存下来,避免重复计算。通常情况下,我们会使用一张二维表来记录状态转移过程中的中间值,例如动态规划求解斐波那契数列问题时,就可以定义一个二维数组f[i][j],代表第i项斐波那契数列中,第j个元素的值。 在LeetCode中,动态规划标签下有众多难度不同的问题。例如,经典的“爬楼梯”问题,要求我们计算到n级楼梯的方案数。这个问题的解法非常简单,只需要维护一个长度为n的数组,记录到达每一级楼梯的方案数即可。类似的问题还有“零钱兑换”、“乘积最大子数组”、“通配符匹配”等,它们都采用了类似的动态规划思想,通过拆分问题、保存中间状态来求解问题。 需要注意的是,动态规划算法并不是万能的,它虽然可以处理众多经典问题,但在某些场景下并不适用。例如,某些问题的状态转移过程比较复杂,或者状态转移方程中存在多个参数,这些情况下使用动态规划算法可能会变得比较麻烦。此外,动态规划算法也存在一些常见误区,例如错用贪心思想、未考虑边界情况等。 总之,掌握动态规划算法对于LeetCode的学习和解题都非常重要。除了刷题以外,我们还可以通过阅读经典的动态规划书籍,例如《算法竞赛进阶指南》、《算法数据结构基础》等,来深入理解这种算法思想。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值