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

【Leetcode hot 100】215.数组中的第K个最大元素

问题链接

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

问题描述

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

示例 1:

输入: [3,2,1,5,6,4], k = 2
输出: 5

示例 2:

输入: [3,2,3,1,2,4,5,5,6], k = 4
输出: 4

提示:

  • 1 <= k <= nums.length <= 10^5
  • -10^4 <= nums[i] <= 10^4

问题解答

解法 1:快速选择算法(最优平均复杂度)

算法原理
快速选择基于 快速排序 的核心思想(分治):

  1. 分区(Partition):随机选择一个“基准值”,将数组分为两部分:左半部分元素 基准值(降序分区,方便直接找“第 k 大”),右半部分元素 < 基准值。
  2. 定位目标
    • 若基准值的索引 pivotIndex 恰好等于 k-1(数组下标从 0 开始),则基准值就是第 k 大元素。
    • pivotIndex > k-1:目标在左半部分,递归处理左区间。
    • pivotIndex < k-1:目标在右半部分,递归处理右区间。

关键优化

  • 随机基准:避免数组有序时(如 [1,2,3,4,5])分区失衡,将时间复杂度从最坏 O(n²) 优化到 平均 O(n)
  • 原地分区:无需额外空间,空间复杂度 O(log n)(递归栈深度)。

Java 代码实现

import java.util.Random;class Solution {private static final Random RANDOM = new Random();public int findKthLargest(int[] nums, int k) {// 快速选择的核心:在 [left, right] 区间找第 k 大元素return quickSelect(nums, 0, nums.length - 1, k - 1); // k-1 是目标的下标}/*** 在 nums[left..right] 中找到下标为 targetIndex 的元素(即第 targetIndex+1 大元素)*/private int quickSelect(int[] nums, int left, int right, int targetIndex) {// 分区:返回基准值的最终下标int pivotIndex = partition(nums, left, right);if (pivotIndex == targetIndex) {// 基准值就是目标元素,直接返回return nums[pivotIndex];} else if (pivotIndex > targetIndex) {// 目标在左半区,递归处理左区间return quickSelect(nums, left, pivotIndex - 1, targetIndex);} else {// 目标在右半区,递归处理右区间return quickSelect(nums, pivotIndex + 1, right, targetIndex);}}/*** 分区函数:随机选基准,将数组分为 [≥基准, 基准, <基准] 三部分,返回基准下标*/private int partition(int[] nums, int left, int right) {// 1. 随机选择基准(避免有序数组分区失衡)int randomPivot = left + RANDOM.nextInt(right - left + 1);// 2. 基准值交换到区间末尾(方便后续遍历比较)swap(nums, randomPivot, right);int pivot = nums[right]; // 基准值int i = left - 1; // i 是“左半区(≥基准)”的最后一个元素下标// 3. 遍历区间 [left, right-1],将 ≥ 基准的元素归入左半区for (int j = left; j < right; j++) {if (nums[j] >= pivot) {i++; // 左半区扩大swap(nums, i, j); // 将当前元素加入左半区}}// 4. 将基准值交换到左半区的末尾(基准值的最终位置)swap(nums, i + 1, right);return i + 1; // 返回基准值的下标}// 交换数组中两个元素的位置private void swap(int[] nums, int i, int j) {int temp = nums[i];nums[i] = nums[j];nums[j] = temp;}
}

解法 2:计数排序(适用于数值范围有限的场景)

算法原理
利用题目中 数值范围固定 的特点(-10⁴ ≤ nums[i] ≤ 10⁴):

  1. 偏移量处理:由于存在负数,用 offset = 10⁴ 将所有数值映射到非负区间(0 ≤ nums[i]+offset ≤ 20000)。
  2. 计数数组:创建长度为 20001 的计数数组 count,统计每个数值出现的次数(count[num+offset]++)。
  3. 逆序遍历计数数组:从最大数值(对应 count[20000])开始累加次数,当累加和 ≥ k 时,当前数值即为第 k 大元素。

Java 代码实现

class Solution {public int findKthLargest(int[] nums, int k) {final int OFFSET = 10000; // 偏移量,将负数映射为非负数int[] count = new int[20001]; // 覆盖 [-10000, 10000] 的所有可能值// 1. 统计每个数值的出现次数for (int num : nums) {count[num + OFFSET]++;}// 2. 逆序遍历计数数组,找第 k 大元素int remaining = k; // 剩余需要找的“大元素”个数for (int i = 20000; i >= 0; i--) {if (count[i] > 0) {if (count[i] >= remaining) {// 当前数值的出现次数足够覆盖剩余需求,返回当前数值(减去偏移量)return i - OFFSET;}// 否则,减去当前数值的出现次数,继续找更小的数值remaining -= count[i];}}// 理论上不会走到这里(题目保证 k 有效)return -1;}
}

两种解法对比

维度快速选择算法计数排序
时间复杂度平均 O(n),最坏 O(n²)严格 O(n + M)(M=20001)
空间复杂度O(log n)(递归栈)O(M)(固定空间,可视为 O(1))
适用场景数值范围大、数据量极大的场景数值范围固定且较小的场景
稳定性不稳定(分区会打乱顺序)稳定
http://www.dtcms.com/a/540506.html

相关文章:

  • Leetcode每日一练--44
  • Leetcode 3728. Stable Subarrays With Equal Boundary and Interior Sum
  • 江科大stm32 | OLED显示汉字
  • vue3前端解析excel文件
  • 5.1.5 大数据方法论与实践指南-数据仓库存储格式选择
  • 网站空间1g多少钱怎么做网站加盟
  • 学校网站怎么做推广上海网站建站多少钱
  • php网站开发心得体会漯河市网站建设
  • 打工人日报#20251028
  • 手写前端脚手架cli
  • 《内蒙古自治区本级政务信息化运行维护项目预算支出方案编制规范和预算支出标准(试行)》(内财预〔2024〕194号)标准解读
  • 在 Spring Boot 项目中使用分页插件的两种常见方式
  • MapReduce运行实例
  • “透彻式学习”与“渗透式学习”
  • 惠洋科技H5528K 100V高耐压2.5A 支持24V30V36V48V60V72V80V降压6V9V12V车灯供电恒流芯片IC 高低亮
  • Spring Boot3零基础教程,Actuator 导入,笔记82
  • 如何用PyQt5实现一个简易计算器应用
  • Spring Boot 事务管理深度解析
  • 【系统分析师】高分论文:软件的系统测试及应用(电子商务门户网站系统)
  • 尚硅谷React扩展笔记
  • 8.模板和string(下)
  • 5G专网客户案例分享:基于可编程5G的工业互联网产线验证系统
  • 前端:前端开发中,实现水印(Watermark)
  • 网站排名方法胶州网站建设 网络推广
  • 潍坊商城网站建设修改wordpress样式
  • AI智能体从系统智能到生态智能:SmartMediaKit 如何成为智能体时代的视频神经系统
  • 【音视频】H.264关键帧识别
  • AI智能相机未来应用
  • grafana做状态变化的监控图表
  • 19.高级的ACL