【Hot 100】215. 数组中的第K个最大元素 堆的相关概念

请添加图片描述

  • 🙋‍♂️ 作者:海码007
  • 📜 专栏:算法专栏
  • 💥 标题:【Hot 100】215. 数组中的第K个最大元素 堆的相关概念
  • ❣️ 寄语:书到用时方恨少,事非经过不知难!

引言

这部分内容涉及排序算法,包括快速排序和堆排序算法。
下面给出几篇博客,可以去了解一下,这个知识:
【STL中堆的相关概念】
【堆排序原理】
【快排原理】

数组中的第K个最大元素

  • 🎈 题目链接:
  • 🎈 做题状态:

我的解题

使用了 快速选择(Quick Select) 的思想,这是解决「数组中第K个最大元素」问题中非常高效的一种方法,平均时间复杂度为 O(n),最坏为 O(n^2)(与快速排序类似)。

下面我将详细解析思路并添加注释版代码


✅ 思路解析

我们希望在一个无序数组中找到第 K 大的元素
快速选择的思路是:

  1. 每次从数组中选一个“基准值”(pivot),并把数组划分成三部分:

    • big: 大于 pivot 的元素
    • equal: 等于 pivot 的元素
    • small: 小于 pivot 的元素
  2. 接着判断目标位置 K 在哪一段:

    • 如果 K 小于等于 big 的个数 → 目标在 big 中,递归找第 K 大
    • 如果 K 落在 equal 的范围 → 目标是 pivot,直接返回
    • 如果 K 大于 big + equal → 目标在 small 中,更新目标为 k - big.size() - equal.size(),递归查找

这种“每次排除一部分数据”的方式是快速选择的本质。


✅ 详细注释版代码

class Solution {
private:
    // 快速选择函数,用于找到 nums 中的第 k 大元素
    int quickSelect(vector<int>& nums, int k)
    {
        // 1. 选择一个基准值(pivot),这里选取第一个元素
        int pivot = nums[0];

        // 2. 创建三个分区:大于、等于、小于 pivot
        vector<int> big, equal, small;
        for (int num : nums)
        {
            if (num > pivot)
            {
                big.push_back(num);       // 放入大于 pivot 的部分
            }
            else if (num < pivot)
            {
                small.push_back(num);     // 放入小于 pivot 的部分
            }
            else
            {
                equal.push_back(num);     // 放入等于 pivot 的部分
            }
        }

        // 3. 判断目标第 k 大元素在哪一部分:

        // 情况一:第 k 大在 big 部分中 → 递归查找
        if (k <= big.size())
        {
            return quickSelect(big, k);
        }

        // 情况二:第 k 大在 small 部分中
        // 剩下的数量:big.size() + equal.size()
        // 如果 k 超过这些,就在 small 里继续找第 (k - 前面两个数量) 大
        if (k > big.size() + equal.size())
        {
            return quickSelect(small, k - big.size() - equal.size());
        }

        // 情况三:第 k 大在 equal 中,说明 pivot 就是我们要找的值
        return pivot;
    }

public:
    // 主函数接口
    int findKthLargest(vector<int>& nums, int k) {
        return quickSelect(nums, k);
    }
};

✅ 举个例子说明流程:

假设:

nums = [3, 2, 1, 5, 6, 4], k = 2

目标是第2大的元素(即是整个数组排序后第2大的那个数)。

  1. 选定 pivot = 3

  2. 划分:

    • big = [5, 6, 4]
    • equal = [3]
    • small = [2, 1]
  3. 因为 k = 2,且 big.size() = 3 >= k,所以第2大在 big

  4. 递归继续处理 [5, 6, 4],重复上述逻辑…

最终会返回 5


✅ 时间复杂度分析

  • 平均时间复杂度: $O(n)$
    每次划分都能有效地减少问题规模

  • 最坏时间复杂度: $O(n^2)$
    如果 pivot 每次都选得不好,例如每次最小/最大值,会退化为排序

  • 空间复杂度: $O(n)$
    因为每次都会重新构造新的子数组(可用 partition + in-place swap 改成 $O(1)$)

划分为两个区间

完全可以只划分两个区间来实现快速选择(Quick Select)——这是经典的、空间更优、执行效率更高的做法,也是许多面试中建议写法。


✅ 两区间快选版本思想

我们不需要将数组划分成「大于 / 等于 / 小于 pivot」三份,而是直接采用**原地 partition(快排的 Lomuto 或 Hoare 分区方案)**将数组划分成两部分:

  • 左边区间:≥ pivot 的元素
  • 右边区间:< pivot 的元素

然后只需要关注当前 pivot 的**下标 pivotIndex**和目标下标 k_index = k - 1(从大到小第 k 个,就是从小到大排好后第 n-k 个)。


✅ 原地快选代码(只划分两区间)

class Solution {
private:
    // 快速选择:寻找第k大的元素(第k大 == 第n-k小)
    int quickSelect(vector<int>& nums, int left, int right, int k_smallest)
    {
        if (left == right) return nums[left];  // 只有一个元素时直接返回

        // 选择最后一个元素作为基准
        int pivot = nums[right];
        int store_index = left;

        // partition过程:把所有小于pivot的元素放在左边
        for (int i = left; i < right; ++i)
        {
            if (nums[i] < pivot) // 注意:这里是升序排列,小的放左边
            {
                swap(nums[store_index], nums[i]);
                store_index++;
            }
        }

        // 把 pivot 放到最终的位置(中间)
        swap(nums[store_index], nums[right]);

        // 判断 pivot 的下标位置和目标位置的关系
        if (store_index == k_smallest)
        {
            return nums[store_index]; // 找到了
        }
        else if (store_index < k_smallest)
        {
            return quickSelect(nums, store_index + 1, right, k_smallest);
        }
        else
        {
            return quickSelect(nums, left, store_index - 1, k_smallest);
        }
    }

public:
    int findKthLargest(vector<int>& nums, int k)
    {
        int n = nums.size();
        // 找第k大的元素 = 找第(n-k)小的元素
        return quickSelect(nums, 0, n - 1, n - k);
    }
};

✅ 时间复杂度分析(和三段式一样)

  • 平均时间复杂度: $O(n)$
  • 最坏时间复杂度: $O(n^2)$(例如 pivot 每次都是最大或最小)
  • 空间复杂度: $O(1)$(原地 partition,无需额外数组)

✅ 总结:两段式 vs 三段式

比较项三段式划分两段式划分(原地 partition)
空间复杂度$O(n)$(使用3个临时数组)$O(1)$ 原地操作
实现复杂度简单清晰,逻辑更直观更接近标准快排、效率更高
工程实用性教学友好,适合理解 quick select实战常用(如 LeetCode 高频解法)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值