如何寻找无序数组中的第K大元素?

有这样一个算法题:有一个无序数组,要求找出数组中的第K大元素。比如给定的无序数组如下所示:

如果k=6,也就是要寻找第6大的元素,很显然,数组中第一大元素是24,第二大元素是20,第三大元素是17...... 第六大元素是9

方法一:排序法

这是最容易想到的方法,先把无序数组从大到小进行排序,排序后的第k个元素自然就是数组中的第k大元素。但是这种方法的时间复杂度是O(nlogn),性能有些差。

方法二:插入法

维护一个长度为k的数组A的有序数组,用于存储已知的K个较大的元素。然后遍历无序数组,每遍历到一个元素,和数组A中的最小元素进行比较,如果小于等于数组A中的最小元素,继续遍历;如果大于数组A中的最小元素,则插入到数组A中,并把曾经的最小元素"挤出去"。

比如K=3,先把最左侧的7,5,15三个数有序放入到数组A中,代表当前最大的三个数。

此时,遍历到3时,由于3<5,继续遍历。

接下来遍历到17,由于17>5,插入到数组A的合适位置,类似于插入排序,并把原先最小的元素5“挤出去”。

继续遍历原数组,一直遍历到数组的最后一个元素......

最终,数组A中存储的元素是24,20,17,代表着整个数组的最大的3个元素。此时数组A中的最小元素17就是我们要寻找的第K大元素。

这个方法的时间复杂度是O(nk),但是如果K的值比较大的话,其性能可能还不如方法一。

小顶堆法

二叉堆是一种特殊的完全二叉树,它包含大顶堆和小顶堆两种形式。其中小顶堆的特点是每一个父节点都小于等于自己的两个子节点。要解决这个算法题,我们可以利用小顶堆的特性。

维护一个容量为K的小顶堆,堆中的K个节点代表着当前最大的K个元素,而堆顶显然是这K个元素中的最小值
遍历原数组,每遍历一个元素,就和堆顶比较,如果当前元素小于等于堆顶,则继续遍历;如果元素大于堆顶,则把当前元素放在堆顶位置,并调整二叉堆(下沉操作)。
遍历结束后,堆顶就是数组的最大K个元素中的最小值,也就是第K大元素

假设K=5,具体操作步骤如下:

1.把数组的前K个元素构建成堆

2.继续遍历数组,和堆顶比较,如果小于等于堆顶,则继续遍历;如果大于堆顶,则取代堆顶元素并调整堆。

遍历到元素2,由于2<3,所以继续遍历。

遍历到元素20,由于20>3,20取代堆顶位置,并调整堆。

遍历到元素24,由于24>5,24取代堆顶位置,并调整堆。

以此类推,我们一个一个遍历元素,当遍历到最后一个元素8时,小顶堆的情况如下:

3.此时的堆顶,就是堆中的最小元素,也就是数组中的第K大元素。

这个方法的时间复杂度是多少呢?

1.构建堆的时间复杂度是O(K)
2.遍历剩余数组的时间复杂度O(n-K)
3.每次调整堆的时间复杂度是O(logk)
其中2和3是嵌套关系,1和2,3是并列关系,所以总的最坏时间复杂度是O((n-k)logk + k)。当k远小于n的情况下,也可以近似地认为是O(nlogk)

这个方法的空间复杂度是多少呢?
刚才我们在详细步骤中把二叉堆单独拿出来演示,是为了便于理解。但如果允许改变原数组的话,我们可以把数组的前K个元素“原地交换”来构建成二叉堆,这样就免去了开辟额外的存储空间。因此空间复杂度是O(1)

代码如下:

/*** 寻找第k大元素* @param array 待调整的数组* @param k 第几大* @return*/public static int findNumberK(int[] array, int k) {//1.用前k个元素构建小顶堆buildHeap(array, k);//2.继续遍历数组,和堆顶比较for (int i = k; i < array.length; i++) {if(array[i] > array[0]) {array[0] = array[i];downAdjust(array, 0, k);}}//3.返回堆顶元素return array[0];}private static void buildHeap(int[] array, int length) {//从最后一个非叶子节点开始,依次下沉调整for (int i = (length - 2) / 2; i >= 0; i--) {downAdjust(array, i, length);}}/*** 下沉调整* @param array 待调整的堆* @param index 要下沉的节点* @param length 堆的有效大小*/private static void downAdjust(int[] array, int index, int length) {//temp保存父节点的值,用于最后的赋值int temp = array[index];int childIndex = 2 * index + 1;while (childIndex < length) {//如果有右孩子,且右孩子小于左孩子的值,则定位到右孩子if (childIndex + 1 < length && array[childIndex + 1] < array[childIndex]) {childIndex++;}//如果父节点小于任何一个孩子的值,直接跳出if (temp <= array[childIndex])break;//无需真正交换,单项赋值即可array[index] = array[childIndex];index = childIndex;childIndex = 2 * childIndex + 1;}array[index] = temp;}public static void main(String[] args) {int[] array = new int[] {7, 5, 15, 3, 17, 2, 20, 24, 1, 9, 12, 8};System.out.println(findNumberK(array, 5));}

方法四:分治法

大家都了解快速排序,快速排序利用分治法,每一次把数组分成较大和较小元素两部分。我们在寻找第K大元素的时候,也可以利用这个思路,以某个元素A为基准,把大于A的元素都交换到数组左边,小于A的元素交换到数组右边。

比如我们选择以元素7作为基准,把数组分成了左侧较大,右侧较小的两个区域,交换结果如下:

包括元素7在内的较大元素有8个,但我们的K=5,显然较大元素的数目过多了。于是我们在较大元素的区域继续分治,这次以元素12为基准:

这样一来,包括元素12在内的较大元素有5个,正好和K相等。所以,基准元素12就是我们所求的。

这就是分治法的思想,这种方法的时间复杂度甚至优于小顶堆法,可以达到O(n)。

转载于:https://www.cnblogs.com/kyoner/p/10465633.html

如何寻找无序数组中的第K大元素?相关推荐

  1. 无序数组中找第K大的数

    类快排算法 leetcode215 由于只要求找出第k大的数,没必要将数组中所有值都排序. 典型解法:快速排序分组. 在数组中找到第k大的元素 取基准元素,将元素分为两个集合,一个集合元素比基准小,另 ...

  2. 数组中的第K大元素问题(C++)

    数组中的第K大元素问题 问题: 在未排序的数组中找到第 k 个最大的元素.请注意,需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素. 约定: 假设这里数组的长度为 n. 方法一: ...

  3. 【算法】快速选择算法 ( 数组中找第 K 大元素 )

    算法 系列博客 [算法]刷题范围建议 和 代码规范 [算法]复杂度理论 ( 时间复杂度 ) [字符串]最长回文子串 ( 蛮力算法 ) [字符串]最长回文子串 ( 中心线枚举算法 ) [字符串]最长回文 ...

  4. DC-leetcode215数组中的第k大元素

    在未排序的数组中找到第 k 个最大的元素.请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素. 示例 1: 输入: [3,2,1,5,6,4] 和 k = 2 输出: 5 ...

  5. 网易_在数组中查找前K个元素

    笔试题,最后一题 查找网易云音乐中播放量最大的前K个歌曲. 换句话说,就是在数组中查找前K大元素. 大致有以下几个思路. 1.第一感觉就是对数组进行降序全排序,然后返回前K个元素,即是需要的K个最大数 ...

  6. 小米笔试题:无序数组中最小的k个数

    题目描述 链接:https://www.nowcoder.com/questionTerminal/ec2575fb877d41c9a33d9bab2694ba47?source=relative 来 ...

  7. 快速查找无序数组中的第K大数?

    1.题目分析: 查找无序数组中的第K大数,直观感觉便是先排好序再找到下标为K-1的元素,时间复杂度O(NlgN).在此,我们想探索是否存在时间复杂度 < O(NlgN),而且近似等于O(N)的高 ...

  8. 【算法】数组与矩阵问题——找到无序数组中最小的k个数

    1 /** 2 * 找到无序数组中最小的k个数 时间复杂度O(Nlogk) 3 * 过程: 4 * 1.一直维护一个有k个数的大根堆,这个堆代表目前选出来的k个最小的数 5 * 在堆里的k个元素中堆顶 ...

  9. 【LeetCode】快排-无序整数数组中找第k大的数(或者最小的k个数)

    一个有代表性的题目:无序整数数组中找第k大的数,对快排进行优化. 这里先不说这个题目怎么解答,先仔细回顾回顾快排,掰开了揉碎了理解理解这个排序算法:时间复杂度.空间复杂度:什么情况下是复杂度最高的情况 ...

最新文章

  1. 前端开发神器之ngrok
  2. java计数器策略模式_策略模式与外观模式 | 学步园
  3. 深度学习入门之PyTorch学习笔记:深度学习介绍
  4. Oracle数据库修改字段类型
  5. 存储变革 IBM V5000四大优势助用户破旧立新
  6. SSH putty Disconnected: Server protocol violation: unexpected SSH2_MSG_UNIMPLEMENTED packet
  7. 在64位Ubuntu上编译32位程序常见错误
  8. php 取数值整数的函数是,PHP取整数函数常用的四种方法小结
  9. 微信挑战者飞聊被下架后 再度火速上线 尚能一战否?
  10. 一个简单混合协议通讯列子,物联网和互联网通讯。
  11. python最大的社区_python 最大流
  12. 【Oracle】删除重复记录
  13. Nifi flow 备份恢复
  14. AVEVA InTouch安全网关 AccessAnywhere 任意文件读取漏洞 CVE-2022-23854
  15. String类题目methods总结
  16. aws打开慢_亚马逊AWS服务器访问较慢,如何快速访问AWS服务器呢?
  17. 【C语言】初识C语言(下篇)
  18. CVer最想知道的,简单分析下《2020年度中国计算机视觉人才调研报告》
  19. 【混合云小知识】混合云应用场景包含哪些?
  20. 英特尔移动CPU命名及列表

热门文章

  1. 高并发图片实时渲染技术在阿里妈妈的大规模应用
  2. chrome jquery ajax请求,jQuery.ajax在Chrome中无法正常执行的解决办法
  3. 灯泡四个闪烁c语言程序设计教程课后答案,c语言编程题及答案4.doc
  4. mysql将查到的数据删除_MySQL数据库的基本操作——增、删、改、查
  5. java上传永久图文素材_Java-微信开发上传永久素材(支持所有文件类型)
  6. 阿里资深技术专家:如何快速成长为技术大牛?
  7. 深入浅出深度学习(四)概率统计基础
  8. 架构垂直伸缩和水平伸缩区别_简单的可伸缩图神经网络
  9. 支付宝18年账单已出,你消费了多少钱?
  10. 大学生每个月1500元的生活费,够吗?