第二天,在另一家公司……

小灰是吧?请简单介绍一下你自己。

好的,blah blah blah……

下面考你一道算法题:

给你一个无序数组,要求你找出数组中的第k大元素。

题目是什么意思呢?比如给定的无序数组如下:

如果 k=6,也就是要寻找数组中的第6大元素,这个元素是哪一个呢?

显然,数组中第一大的元素是24,第二大的元素是20,第三大的元素是17 ......第6大的元素是9

让我想想啊……

对了,我可以先把无序数组排序,然后数出排序后的第k个元素!

方法1:排序法

这是最容易想到的方法,先把无序数组从大到小进行排序,排序后的第k个元素,自然就是数组中的第k大元素。

先进行排序的话,算法时间复杂度是O(nlogn),

性能有些差,有没有更优化的方法?

让我想想啊……

对了,我可以维护一个长度为k的数组,有序存储当前k个较大的元素!

方法2:插入法

维护一个长度为k的有序数组A,用于存储已知的k个较大的元素。

接下来遍历原数组,每遍历到一个元素,和数组A中最小的元素相比较,如果小于等于数组A的最小元素,继续遍历;如果大于数组A的最小元素,则插入到数组A中,并把曾经的最小元素“挤出去”。

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

这时候,遍历到元素3, 由于3<5,继续遍历。

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

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

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

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

还有没有更优化的方案?

好像没有更快的方法了吧……

呵呵,没关系,回家等通知去吧!

解题思路

小灰,你刚刚去面试了?结果怎么样?

唉……

大黄,要想找到无序数组中的第k大元素,有什么性能较高的方法吗?

这是一道很经典的算法题,解法有很多种,

其中最容易想到的是利用二叉堆来解决。

关于二叉堆的概念,在上一本《漫画算法》中我们介绍过。简而言之,二叉堆是一种特殊的完全二叉树,它包含最大堆和最小堆两种形式。

其中最小堆的特点,是每一个父结点都小于等于自己的子结点,堆顶是整个堆中最小的结点。要解决这个算法题,我们可以利用最小堆的特性。

可是,最小堆和这个算法题究竟有什么关系呢?

别急,让我来解释一下这个方法的思路。

方法3:最小堆法

维护一个容量为k的最小堆,堆中的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大元素。

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

  • 构建堆的时间复杂度是 O(k)

  • 遍历剩余数组的时间复杂度是O(n-k)

  • 每次调整堆的时间复杂度是 O(logk)

其中2和3是嵌套关系,1和2,3是并列关系,所以总的最坏时间复杂度是O((n-k)logk + k)。当k远小于n的情况下,也可以近似地认为是O(nlogk)

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

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

因此,这个方法的空间复杂度是O(1)。

明白了,最小堆法还真是个巧妙的解决方法!怎么用代码来实现呢?

代码很简单,让我们来看一看:


public class KthLargestNumber {/*** 寻找第k大的元素* @param array  待调整的堆* @param k  第几大*/public static int findKthLargestNumber(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];}/*** 构建堆* @param array  待调整的堆* @param length  堆的有效大小*/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(findKthLargestNumber(array, 5));}
}

原来如此,这下彻底明白啦!

要寻找数组的第k大元素,其实还有一种方法,这种方法就是分治法

方法4:分治法

大家都了解快速排序,快速排序利用分治法,每一次把数组分成较大和较小的两部分。

我们在寻找第k大元素的时候,也可以利用这个思路,以某个元素a为基准,把大于a的元素都交换到数组左边,小于a的元素都交换到数组右边。

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

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

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

这就是分治法的大体思想,这种方法的时间复杂度甚至优于最小堆法,可以达到O(n)。有兴趣的小伙伴可以尝试用代码实现一下。

好了,关于寻找数组第k大元素的方法,我们就介绍到这里,

想要了解更多内容,可以阅读《漫画算法2》哦!

▊《漫画算法2:小灰的算法进阶

魏梦舒(@程序员小灰) 著

  • 全网阅读量近2000万的漫画算法故事

  • 和快乐的小仓鼠一起搞定数据结构和算法,从容面试

本书是《漫画算法:小灰的算法之旅》的续作,通过主人公小灰的心路历程,用漫画的形式讲述了多个数据结构、算法及复杂多变的算法面试题目。

抽奖赠书

截止时间:2021年6月23日 17:00

如何抽奖:点击下方卡片,关注并回复关键词 :20210619

下次你更希望我们送哪本书呢?

留言告诉我们!

趣解面试高频算法难题:数组中的第K个最大元素相关推荐

  1. ⭐算法入门⭐《哈希表》中等05 —— LeetCode 215. 数组中的第K个最大元素

    文章目录 一.题目 1.题目描述 2.基础框架 3.原题链接 二.解题报告 1.思路分析 2.时间复杂度 3.代码详解 三.本题小知识 四.加群须知 一.题目 1.题目描述   给定整数数组nums和 ...

  2. 215.数组中的第K个最大元素/347. 前 K 个高频元素

    2020-05-25 1.题目描述 数组中的第K个最大元素 2.题解 1.使用冒泡排序 2.使用快速排序 3.优先队列(内部是用堆来实现的) 3.代码 class Solution { public: ...

  3. 在数组中查找第k个最大元素_查找数组中每个元素的最近最大邻居

    在数组中查找第k个最大元素 Problem statement: 问题陈述: Given an array of elements, find the nearest (on the right) g ...

  4. LeetCode——215,数组中的第K个最大元素(分治法)

    尹成老师带你学算法 /*** \* Created: liuhuichao* \* Date: 2020/1/27* \* Time: 11:32 上午* \* Description: 数组中的第K ...

  5. 力扣(LeetCode)215. 数组中的第K个最大元素(C语言)

    一.环境说明 本文是 LeetCode 215题 : 数组中的第K个最大元素,使用c语言实现. 快速选择.查找无序数组的利器! 测试环境:Visual Studio 2019. 二.代码展示 void ...

  6. LC215. 数组中的第K个最大元素

    题目介绍 数组中的第K个最大元素 给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素. 请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素. 输入: [ ...

  7. 数组中的第K个最大元素

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

  8. 【LeetCode】【HOT】215. 数组中的第K个最大元素(优先队列)

    [LeetCode][HOT]215. 数组中的第K个最大元素 文章目录 [LeetCode][HOT]215. 数组中的第K个最大元素 package hot;import java.util.Co ...

  9. 215. 数组中的第 K个最大元素

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

最新文章

  1. matlab-等高线图-三维曲线的绘制
  2. python 读取配置文件config_python中读取配置文件ConfigParser
  3. Win10+libtorch1.1+opencv 笔记
  4. 人人都会设计模式:03-策略模式--Strategy
  5. 如何查看mysql的gtid_汇总丨MySQL GTID技术点,看这一篇就够了!
  6. WSS 3.0与MOSS 2007重要更新
  7. 3个免费开源的项目管理/甘特图软件
  8. 盘点10种局域网联机游戏
  9. 安卓dj专业打碟机软件_Mac平台上的专业DJ打碟软件
  10. 全球股市币市同迎黑色一天 熊市持现金或是王道 | 链塔快评
  11. 桌面计算机未响应怎么办,电脑卡死程序未响应的解决方法步骤详解
  12. OSChina 周日乱弹 —— 我重新说
  13. 阿里云的服务器、腾讯云的域名,域名解析失败的解决方案
  14. ChinaSoft 论坛巡礼 | 优秀博士生论坛
  15. Apache Bench的安装与使用(前端使用apache-bench进行并发测试)
  16. Android离线人脸识别方案对比
  17. Excel提取单元格中的数字
  18. 【5. ROS机器人的运动控制】
  19. linux上 用unzip命令解压带密码保护的 zip 文件报错 unsupported compression method 99
  20. 目标检测——卷积神经网路基础知识

热门文章

  1. Linux命令之cal
  2. BZOJ 2197 [Usaco2011 Mar]Tree Decoration
  3. linux图形界面基本知识(X、X11、Xfree86、Xorg、GNOME、KDE之间的关系)
  4. 额外域建立FSMO角色转移及夺取
  5. Spring(AbstractRoutingDataSource)实现动态数据源切换
  6. linux perl 报错 Can‘t locate CPAN.pm in @INC (@INC contains: inc /usr/local/lib64/perl5 /usr.... 解决方法
  7. golang 获取切片 slice 第一个 最后一个 元素
  8. ATTCK框架简介 已知攻击技术汇总
  9. golang 数据库 连接与连接池
  10. python3 module 'pexpect' has no attribute 'spawn' 解决方法