2024年面试高频算法难题:数组中的第K个最大元素,java基础面试题及答案

总结

上述知识点,囊括了目前互联网企业的主流应用技术以及能让你成为“香饽饽”的高级架构知识,每个笔记里面几乎都带有实战内容。

很多人担心学了容易忘,这里教你一个方法,那就是重复学习。

打个比方,假如你正在学习 spring 注解,突然发现了一个注解@Aspect,不知道干什么用的,你可能会去查看源码或者通过博客学习,花了半小时终于弄懂了,下次又看到@Aspect 了,你有点郁闷了,上次好像在哪哪哪学习,你快速打开网页花了五分钟又学会了。

从半小时和五分钟的对比中可以发现多学一次就离真正掌握知识又近了一步。

人的本性就是容易遗忘,只有不断加深印象、重复学习才能真正掌握,所以很多书我都是推荐大家多看几遍。哪有那么多天才,他只是比你多看了几遍书。

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

图片图片还有没有更优化的方案?图片

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

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

解题思路

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

图片图片唉……

图片图片大黄,要想找到无序数组中的第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的元素都交换到数组右边。

总结

我们总是喜欢瞻仰大厂的大神们,但实际上大神也不过凡人,与菜鸟程序员相比,也就多花了几分心思,如果你再不努力,差距也只会越来越大。

面试题多多少少对于你接下来所要做的事肯定有点帮助,但我更希望你能透过面试题去总结自己的不足,以提高自己核心技术竞争力。每一次面试经历都是对你技术的扫盲,面试后的复盘总结效果是极好的!

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

(img-BwKM5Tdc-1715275286547)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值