当前位置: 首页 > news >正文

LeetCode刷题记录----347.前K个高频元素(Medium)

2025/9/17

题目(Medium):

347. 前 K 个高频元素


我的思路:

模式识别:在一组数据中,找到前几大的元素 --->可以考虑用堆来处理

思路1:最大堆

因为是要返回前K大的元素,我们可以构建一个最大堆,然后把所有元素都入堆之后。再把堆中的 K 个出堆,即可得到需要的K个元素

不过这里要的是出现频率的前K大,所以需要先构建出现数字和出现频率的字典映射。而最大堆,可以用优先队列,里面优先级用当前加入堆中元素的负数表示即可。

所以大致步骤是:

①遍历原始数组,并用字典记录每个数字的出现频率

②遍历字典,用优先队列构建最大堆

③把最大堆出堆K次,并把出堆元素加入到数组中。出完后即为答案

具体代码如下:

public class Solution {public int[] TopKFrequent(int[] nums, int k) {Dictionary<int, int> freDic = new Dictionary<int, int>();PriorityQueue<int, int> maxHeap = new PriorityQueue<int, int>();foreach(var num in nums){if(!freDic.ContainsKey(num))freDic.Add(num, 1);elsefreDic[num]++;}foreach(var key in freDic.Keys){maxHeap.Enqueue(key, -freDic[key]);}int[] res = new int[k];for(int i = 0; i < k; i++){res[i] = maxHeap.Dequeue();}return res;}
}

时间复杂度:O(NlogN)【这里影响最大的是遍历字典并入堆的部分,最坏的情况下字典的长度可以达到N,即原始数组中每个数字都不同。而堆中也会相应需要插入这么多数字】

空间复杂度:O(N)【字典或者堆中最多会储存N个元素】

思路2:最小堆

或者我们也可以用最小堆,只是一直保持最小堆中只有K个元素。当超过K个元素的时候,就会出堆,这样每次就可以把最小的元素排除。遍历完全之后剩下的堆中元素就是前K大元素了。

具体代码如下:

public class Solution {public int[] TopKFrequent(int[] nums, int k) {Dictionary<int, int> freDic = new Dictionary<int, int>();PriorityQueue<int, int> minHeap = new PriorityQueue<int, int>();foreach(var num in nums){freDic[num] = freDic.GetValueOrDefault(num, 0) + 1;}//让最小堆中只保留k个最大的元素foreach(var key in freDic.Keys){minHeap.Enqueue(key, freDic[key]);if(minHeap.Count > k)minHeap.Dequeue();}int[] res = new int[k];for(int i = 0; i < k; i++)res[i] = minHeap.Dequeue();return res;}
}

时间复杂度:O(NlogK)【遍历数组长度为N ,堆中最多有K个元素】

空间复杂度:O(N)【哈希表最多有N个元素】

这里是利用了最小堆每次出堆就是排除当前最小元素的特性。


优化思路:

模式识别:一组确定的数中,找到前K大的数 --->可以用快速排序中的基准值排序部分逻辑处理

思路:因为快速排序中,第一步是选定一个基准值,并把比它大和小的元素分别放置在两侧。而我们正好需要获取的是前K大的元素,而这前K大具体是怎么排列的我们并不关心。

因此,举例说明,选定好基准值4并排序好后数组为[5,6,4,2,3],基准值索引值为【2】,。我们把比4大的排在4的左边,如果k = 1,则我们可以发现前1大元素一定是在左半区间,也就是【0,2】这个范围,那我们只要再递归地缩小区间去【0,1】上继续检索即可。

那如果k = 4呢,此时左半区间【0,2】肯定都是前4大元素的,那就还剩下4-3 = 1个元素,在右半区间,而右半区间还没排序,因此我们也递归地缩小区间去【3,4】上检索即可。

注意当我们比较的时候,是通过频率比较,而具体加入答案数组的时候则是加入原始的值。这两个数据我们都需要,因此用元组形式(int, int)来对这两个信息同时进行存储。

还有,对于出现频率的统计,我们依然采样字典的形式。

具体代码如下:

public class Solution {private List<(int, int)> pairs = new List<(int, int)>();    //存储(原始数字,出现频率)private List<int> res = new List<int>();            //答案列表public int[] TopKFrequent(int[] nums, int k) {Dictionary<int, int> freDic = new Dictionary<int, int>();foreach(var num in nums){freDic[num] = freDic.GetValueOrDefault(num, 0) + 1;}foreach(var key in freDic.Keys){pairs.Add((key, freDic[key]));}Qsort(0, pairs.Count-1, k);return res.ToArray();    //把列表转为数组返回}public void Swap(int i, int j){if(i == j)  return;var temp = pairs[i];pairs[i] = pairs[j];pairs[j] = temp;}public void Qsort(int left, int right, int k){int randomIndex = new Random().Next(left, right+1);//int randomIndex = left;int priovt = pairs[randomIndex].Item2;Swap(randomIndex, left);int store_Index = left;for(int i = left+1; i <= right; i++){if(pairs[i].Item2 > priovt){Swap(store_Index + 1, i);store_Index++;}}Swap(store_Index, left);    //最后把基准值换到正确的位置Console.WriteLine("store_index = " + store_Index + " , priovt = " + priovt);//接下来根据基准值所在的索引值和k的大小关系来获取前k大的数字if(k <= store_Index - left){//说明在左半区间,需要进一步减小范围Qsort(left, store_Index-1, k);}else{//说明是左半区的全部 + 右半区的一些for(int i = left; i <= store_Index; i++)res.Add(pairs[i].Item1);//看看右半区还剩多少if(k - (store_Index - left + 1) > 0){//对剩下的右半区再进行排序缩小区间Qsort(store_Index + 1, right, k-(store_Index-left+1));}}}
}

时间复杂度:O(N^2)【每次选择基准值为左右两端的话就会这样,不过如果是选择随机位置的话,平均时间复杂度可以达到O(N)】

空间复杂度:O(N)【最坏情况字典空间为N,排序数组的空间为N,快速排序递归深度为logN】


总结:

①对于在一组数据中,获取其中第K大/小,或者前K大/小的问题,可以通过堆或者快速选择来处理

②对于前K大,这里可以利用最小堆每次弹出最小值的原理,在第一次遍历数组的时候,可以直接入堆,然后把最小值弹出,这样里面保留的就一直是前K大的元素

③对于快速选择,注意的是对基准值位置分割的左右区间的含义,以及对K的大小比较的关系的了解。


文章转载自:

http://qsm3l4x4.pbpcj.cn
http://p9uT7lvq.pbpcj.cn
http://WRCDuOm5.pbpcj.cn
http://jMtGTNhE.pbpcj.cn
http://loUO0Mdb.pbpcj.cn
http://lmeFUoFI.pbpcj.cn
http://6HWpgTcW.pbpcj.cn
http://j00VPgk6.pbpcj.cn
http://1BbcaqjS.pbpcj.cn
http://d5OaUlga.pbpcj.cn
http://IhnZqCt2.pbpcj.cn
http://d2ZKbs1P.pbpcj.cn
http://2U3X7S4a.pbpcj.cn
http://ZG17LElZ.pbpcj.cn
http://EnOznnAK.pbpcj.cn
http://u5keYfXx.pbpcj.cn
http://DHrwKbVw.pbpcj.cn
http://hz4LJxnj.pbpcj.cn
http://phBbYE4T.pbpcj.cn
http://nHDiUHSQ.pbpcj.cn
http://3SgieZLc.pbpcj.cn
http://s5fxWOWi.pbpcj.cn
http://7syNnJqO.pbpcj.cn
http://FbBXqDSo.pbpcj.cn
http://kMXqHzbj.pbpcj.cn
http://2ZLGImr3.pbpcj.cn
http://p3JTkLaP.pbpcj.cn
http://6D1Cuyl8.pbpcj.cn
http://f0Hrdsqr.pbpcj.cn
http://G8swFsP1.pbpcj.cn
http://www.dtcms.com/a/387437.html

相关文章:

  • Windows 部署hexo并启动自己的博客
  • 建议对下载的geo原始数据进行低表达基因过滤**,这是数据预处理的关键步骤之一,可提升后续分析(如差异表达、WGCNA)的准确性和可靠性
  • MySQL 数据库备份与恢复
  • SQLite 数据库简介
  • Java进阶教程,全面剖析Java多线程编程,线程的优先级,笔记07
  • YOLOv12目标检测:使用自定义数据集训练 YOLOv12 检测坑洞严重程度
  • 计算机操作系统学习(五、输入输出管理)
  • Rocksteady开发新《未来蝙蝠侠》游戏 有望登陆PS5/PS6
  • Python爬虫实战——使用NetNut网页解锁器获取亚马逊电商数据的入门指南
  • 【 mq】 mq学习笔记
  • 科学研究系统性思维的理论基础:传统研究工具应用
  • Java基础:基本数据类型与变量(详解)
  • VsCode中配置Git-Bash终端
  • 《无人机政务应用视频图像服务成本度量规范》(T/DGAG025-2024)标准解读
  • 2/3维旋转矩阵推导与助记--记录
  • 【代码随想录算法训练营——Day15】二叉树——110.平衡二叉树、257.二叉树的所有路径、404.左叶子之和、222.完全二叉树的节点个数
  • 《从终端到内核:Linux 指令体系的入门与技术解构(第二篇)》
  • 实验5:组件应用(4学时)
  • 精选40道Kafka面试
  • web自动化随笔
  • HarmonyOS 多线程编程:Worker 使用与性能优化指南
  • 卫星通信大爆发:未来,你的手机将不再“失联”
  • 带你了解STM32:EXTI外部中断
  • Charles抓包工具新手入门教程 安装配置、手机代理与基础使用指南
  • 鸿蒙智能设备自动诊断实战:从传感器采集到远程上报的完整实现
  • 第五章 Arm C1-Premium 内存管理单元详解
  • 第七章 Arm C1-Premium L1数据内存系统解析
  • ARM(10) - I2C
  • 计算机视觉(opencv)实战二十六——背景建模与运动目标检测
  • 《详解Maven的继承与聚合》一篇理解分模块设计理念,以及私服的使用