【分治-2】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. 用 sort 把数组升序排列

  2. 倒数第 k 个元素就是第 k 大元素

  3. 下标 = nums.size() - k

代码实现:

cpp 复制代码
class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        sort(nums.begin(), nums.end());
        return nums[nums.size() - k];
    }
};

复杂度分析:

维度 复杂度 说明
时间复杂度 O(n log n) sort 的复杂度
空间复杂度 O(log n) 递归栈(内省排序)

优缺点:

优点 缺点
✅ 代码极简,一行搞定 ❌ 时间复杂度 O(n log n),不是最优
✅ 不容易出错 ❌ 面试官会追问"能否优化到 O(n)"
✅ 适合快速写出答案 ❌ 对数组做了完全排序,浪费了信息

关键问题 :题目只要求找第 k 大,不需要完全排序。排序反而做了很多额外工作。

方法二:快速选择

解题思路:

基于快速排序的 partition:

  1. 随机选一个 pivot,把数组分成两部分:

    • 左边 ≤ pivot

    • 右边 ≥ pivot

  2. 判断第 k 大在左边还是右边

  3. 只递归包含第 k 大的那一半,另一半不用管

核心优化:每次排除一半,平均时间复杂度 O(n)。

具体过程示例:

nums = [3,2,1,5,6,4], k = 2(找第 2 大,即第 5 小)

复制代码
目标下标: n - k = 6 - 2 = 4(升序后下标4)

第1次 partition: pivot=4
[3,2,1,4,5,6]
         ↑
       pivot在位置3

目标下标4 > 3 → 在右半部分 [5,6] 找

第2次 partition: pivot=5
[5,6]
 ↑
pivot在位置4

目标下标4 == 4 → 找到!返回 5 ✅

代码实现:

cpp 复制代码
class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        int target = nums.size() - k;  // 第 k 大 = 升序后下标 target
        return quickSelect(nums, 0, nums.size() - 1, target);
    }
    
private:
    int quickSelect(vector<int>& nums, int left, int right, int target) {
        if (left == right) return nums[left];
        
        // 随机选 pivot,避免最坏情况
        int pivotIndex = left + rand() % (right - left + 1);
        swap(nums[left], nums[pivotIndex]);
        int pivot = nums[left];
        
        // partition
        int i = left, j = right;
        while (i < j) {
            while (i < j && nums[j] >= pivot) j--;
            while (i < j && nums[i] <= pivot) i++;
            if (i < j) swap(nums[i], nums[j]);
        }
        swap(nums[left], nums[i]);
        
        // 判断 target 在哪边
        if (i == target) return nums[i];
        else if (i < target) return quickSelect(nums, i + 1, right, target);
        else return quickSelect(nums, left, i - 1, target);
    }
};

复杂度分析:

维度 复杂度 说明
时间复杂度 平均 O(n),最坏 O(n²) 随机 pivot 避免最坏
空间复杂度 O(log n) 递归栈

优缺点:

优点 缺点
✅ 平均时间 O(n),最优 ❌ 最坏情况 O(n²)(随机化后概率极低)
✅ 原地修改,空间 O(1)(不算递归栈) ❌ 代码比排序法复杂
✅ 面试首选 ❌ 需要理解 partition

方法三:小根堆

解题思路:

维护一个大小为 k 的小根堆:

  1. 遍历数组,把元素加入堆

  2. 堆大小超过 k 时,弹出堆顶(最小值)

  3. 遍历结束后,堆顶就是第 k 大

代码实现:

cpp 复制代码
class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        priority_queue<int, vector<int>, greater<int>> pq;  // 小根堆
        
        for (int num : nums) {
            pq.push(num);
            if (pq.size() > k) {
                pq.pop();  // 弹出最小的
            }
        }
        
        return pq.top();
    }
};

复杂度分析:

维度 复杂度 说明
时间复杂度 O(n log k) 每个元素入堆 O(log k)
空间复杂度 O(k) 堆的大小

优缺点:

优点 缺点
✅ 适合海量数据流 ❌ 比快速选择慢
✅ 不需要修改原数组 ❌ 需要额外 O(k) 空间
✅ 代码简洁

三种方法对比:

方法 时间复杂度 空间复杂度 是否修改原数组 推荐度
排序法 O(n log n) O(log n) ✅ 修改 ⭐⭐⭐
快速选择 平均 O(n) O(log n) ✅ 修改 ⭐⭐⭐⭐⭐
小根堆 O(n log k) O(k) ❌ 不修改 ⭐⭐⭐⭐
相关推荐
lcj25111 小时前
【C++】set和map——详细使用说明
开发语言·c++·笔记·面试
潼心1412o1 小时前
C++初阶(长期更新)第3讲:类和对象(中)
开发语言·c++
CQU_JIAKE1 小时前
9.15【A】
算法·动态规划
成为深度学习高手1 小时前
DAG:沿时间与通道双相关建模的外生变量时序预测
网络·人工智能·深度学习·算法·机器学习·数据挖掘·时序数据库
程序喵大人1 小时前
【C++入门】编译链接模型 - 02 预处理把头文件怎样塞进源文件
开发语言·c++·预处理·编译链接·头文件·源文件
阳明山水2 小时前
CEDAR双重解耦实现决策与干预分离
人工智能·深度学习·算法·机器学习·架构
w_zero_one2 小时前
链表(3)
java·数据结构·算法
Bruce_Liuxiaowei2 小时前
元组与具名元组:把「第 3 个字段」变成「price」
数据结构·python·语法·元组
Zootopia6262 小时前
美国载人飞船Crew-13明晚发射!
人工智能·算法·机器学习·数学建模·无人机·创业创新·信息与通信