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

【LeetCode 热题 100】215. 数组中的第K个最大元素——(解法一)快速选择

Problem: 215. 数组中的第K个最大元素
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

文章目录

  • 整体思路
  • 完整代码
  • 时空复杂度
    • 时间复杂度:O(N) (平均情况), O(N^2) (最坏情况)
    • 空间复杂度:O(1)

整体思路

这段代码旨在解决一个经典的 Top-K 问题:数组中的第K个最大元素 (Kth Largest Element in an Array)。问题要求在一个未排序的数组中,找到第 k 大的元素。

该算法采用了一种非常高效的、基于 快速排序 (QuickSort) 思想的 快速选择 (Quickselect) 算法。与完整排序不同,快速选择算法只关心目标元素所在的分区,从而避免了对整个数组进行完全排序,大大降低了平均时间复杂度。

  1. 问题转化

    • 算法首先将“找到第 k 大的元素”这个问题,转化为“找到排序后索引为 n-k 的元素”。
    • 例如,在一个长度为 5 的数组中找第 2 大的元素,等价于找排序后索引为 5-2=3 的元素(假设索引从0开始)。这个 target = n-k 就是我们的目标索引。
  2. 核心:partition 分区函数

    • partition 函数是快速选择算法的心脏。它的作用是,在数组的 [left, right] 区间内,随机选择一个元素作为 枢轴 (pivot),然后将数组重新排列,使得:
      • 所有小于 pivot 的元素都在 pivot 的左边。
      • 所有大于 pivot 的元素都在 pivot 的右边。
    • partition 函数最终会返回 pivot 元素在分区后的最终索引 p
    • 随机化:代码通过 RANDOM.nextInt 随机选择一个 pivot,然后将其与区间的第一个元素交换。这是一个至关重要的优化,它可以极大地避免在遇到有序或接近有序的数组时,算法退化到最坏的 O(N^2) 情况。
  3. 迭代选择与缩小范围

    • findKthLargest 方法的主体是一个 while(true) 循环,它不断地调用 partition 函数并缩小搜索范围,直到找到目标。
    • 在每次分区后,我们得到枢轴的最终索引 p。然后比较 p 和我们的目标索引 target
      • 如果 p == target:这说明我们选中的 pivot 正好就是我们要找的第 k 大的元素。我们非常幸运,直接返回 nums[p] 即可。
      • 如果 p < target:这说明目标元素在 pivot右侧。我们就可以完全忽略左半部分,只需在 [p+1, right] 这个更小的区间内继续搜索。
      • 如果 p > target:这说明目标元素在 pivot左侧。我们就可以完全忽略右半部分,只需在 [left, p-1] 这个更小的区间内继续搜索。
    • 这个过程不断重复,每次都将搜索范围缩小,直到找到目标。

完整代码

class Solution {// 使用一个静态的 Random 实例,以当前时间为种子,用于随机化选择private final static Random RANDOM = new Random(System.currentTimeMillis());/*** 在未排序的数组中找到第 k 大的元素。* @param nums 整数数组* @param k 第 k 大* @return 第 k 大的元素值*/public int findKthLargest(int[] nums, int k) {int n = nums.length;int left = 0;int right = n - 1;  // 核心转化:第 k 大的元素,等价于排序后索引为 n-k 的元素。int target = n - k;// 使用循环不断缩小搜索范围,直到找到目标索引while (true) {// 对当前 [left, right] 区间进行分区,返回枢轴的最终位置 pint p = partition(nums, left, right);if (p == target) {// 如果枢轴的位置就是目标位置,则找到了答案return nums[p];} else if (p < target) {// 如果枢轴位置在目标左侧,说明目标在右半部分,更新左边界left = p + 1;} else { // p > target// 如果枢轴位置在目标右侧,说明目标在左半部分,更新右边界right = p - 1;}}}/*** 对 nums 数组的 [left, right] 区间进行分区操作(三路快排的变体)。* @param nums 数组* @param left 区间左边界* @param right 区间右边界* @return 枢轴元素分区后的最终索引*/private int partition(int[] nums, int left, int right) {// 随机化:随机选择一个索引作为枢轴,避免最坏情况int randomIndex = left + RANDOM.nextInt(right - left + 1);swap(nums, left, randomIndex);// 选择区间的第一个元素作为枢轴 pivotint pivot = nums[left];// le: 小于 pivot 的区域的右边界// ge: 大于 pivot 的区域的左边界int le = left + 1;int ge = right;while (true) {// 从左向右找到第一个 >= pivot 的元素while (le <= ge && nums[le] < pivot) {le++;}// 从右向左找到第一个 <= pivot 的元素while (le <= ge && nums[ge] > pivot) {ge--;}// 如果两个指针交错,分区完成if (le >= ge) {break;}// 交换找到的两个元素,使数组更有序swap(nums, le, ge);// 移动指针继续下一轮查找le++;ge--;}// 将枢轴元素放到其最终位置(ge 指向的位置)swap(nums, left, ge);return ge;}/*** 辅助函数:交换数组中两个索引的元素。*/private void swap(int[] nums, int i, int j) {int t = nums[i];nums[i] = nums[j];nums[j] = t;}
}

时空复杂度

时间复杂度:O(N) (平均情况), O(N^2) (最坏情况)

  1. 平均情况
    • 由于我们每次都随机选择 pivot,可以期望 partition 操作每次都能将搜索区间大约减半。
    • 第一次分区,处理 N 个元素。第二次,处理大约 N/2 个元素。第三次,处理大约 N/4 个元素,以此类推。
    • 总的操作次数是一个等比数列求和:N + N/2 + N/4 + ... + 1。这个级数的和收敛于 2N
    • 因此,平均情况下的时间复杂度为 O(N)
  2. 最坏情况
    • 尽管随机化大大降低了最坏情况的概率,但理论上仍然存在。
    • 最坏情况发生在我们每次选择的 pivot 都是当前区间的最大或最小值。这会导致分区后,搜索范围只减少了 1。
    • 此时,总操作次数为 N + (N-1) + (N-2) + ... + 1,这是一个 O(N^2) 的过程。
    • 然而,在实际应用中,由于随机化的存在,这种最坏情况几乎不可能发生。

空间复杂度:O(1)

  1. 主要存储开销:该算法是原地的,它直接在输入的 nums 数组上进行修改,没有使用任何与输入规模 N 成比例的额外数据结构。
  2. 辅助变量:算法只使用了 n, left, right, target, p, le, ge, pivot 等固定数量的变量。
  3. 递归深度:代码使用的是迭代(while 循环)而非递归,因此没有因递归调用而产生的栈空间开销。

综合分析
算法所需的额外辅助空间是常数级别的。因此,其空间复杂度为 O(1)

参考:liweiwei1419

http://www.dtcms.com/a/315304.html

相关文章:

  • CentOS卸载、安装MySQL8(yum操作)
  • 肾上腺疾病AI诊疗一体化系统应用方向探析
  • 智能图书馆管理系统开发实战系列(七):CMake构建系统与持续集成
  • 【Node.js从 0 到 1:入门实战与项目驱动】1.2 Node.js 的核心优势(非阻塞 I/O、事件驱动、单线程模型)
  • 比起登天,孙宇晨更需要安稳着陆
  • 飞算 JavaAI:为软件安全上锁的智能守护者
  • Antlr学习笔记 02、使用antlr4实现简易版计算器
  • 【Z字形变换】
  • 离线Docker项目移植全攻略
  • 明远智睿T113-i核心板:工业设备制造领域成本控制的破局者
  • NX982NX984美光固态闪存NX992NY102
  • UVa1480/LA5034 Jewel
  • webm 读取解析
  • Linux 系统重置用户密码指南
  • go安装gin
  • JMeter 性能测试工具使用教程
  • 【网络运维】Linux:逻辑卷管理
  • Spring @Component注解全解析
  • 计算机网络:子网掩码在路由转发中的关键作用
  • css的选择器
  • 虚拟机中查看和修改文件权限
  • MoonBit Pearls Vol.04:用MoonBit 探索协同式编程
  • Spring Security 认证与授权实现机制
  • 随机森林知识点整理:从原理到实战
  • 课题学习4——将原系统的BERT换为SBERT
  • 【网络运维】Linux:RAID存储技术
  • 单类别目标检测中的 Varifocal Loss 与 mAP 评估:从原理到实践(特别前景和背景类区分)
  • Transformer核心机制:QKV全面解析
  • 图片处理工具类:基于 Thumbnailator 的便捷解决方案
  • Unsloth 大语言模型微调工具介绍