跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

算法实战:快速选择解决第 K 大元素与最小 K 个数问题

快速选择算法解决第 K 大元素与最小 K 个数问题。利用三路划分策略,平均时间复杂度 O(N),优于全排序。C++ 实现包含随机基准选择与递归分区逻辑,无需完整排序即可定位目标区间。

晚风告白发布于 2026/3/15更新于 2026/9/354 浏览
算法实战:快速选择解决第 K 大元素与最小 K 个数问题

算法实战:快速选择解决第 K 大元素与最小 K 个数问题

在处理数组查找类问题时,全排序往往显得过于重手。今天我们来聊聊如何利用**快速选择算法(Quick Select)**高效解决两个经典场景:数组中的第 K 个最大元素,以及最小的 K 个数。

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

题目描述

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

215. 数组中的第 K 个最大元素 - 力扣(LeetCode)

核心思路

很多人第一反应是排序后取索引,但时间复杂度是 O(NlogN)。其实我们可以利用快排的分区思想进行优化。

在标准的三路快排中,我们将数组划分为 [小于基准]、[等于基准]、[大于基准] 三个区域。对于找第 K 大的数,我们只需要关注这三个区域的长度:

  1. 如果**右侧区域(大于基准)**的元素个数 >= k,说明目标就在右边,递归进入右区间。
  2. 如果**中间区域(等于基准)**包含了目标位置,那么基准值本身就是答案。
  3. 否则,目标在左侧,调整 k 值后递归左区间。

这种策略下,平均时间复杂度可以逼近 O(N)。

C++ 代码实现

class Solution {
public:
    int Top_k(vector<int>& nums, int left, int right, int k) {
        if (left == right) {
            return nums[left];
        }
        
        int l = left - 1, r = right + 1, i = left;
        // 随机选择基准元素,避免最坏情况
        int key = nums[rand() % (right - left + 1) + left];
        
        // 三路划分:大于 key | 等于 key | 小于 key
        while (i < r) {
            if (nums[i] > key) {
                swap(nums[i], nums[--r]);
            } else if (nums[i] < key) {
                swap(nums[i++], nums[++l]);
            } else {
                i++;
            }
        }
        
        // 若右边区域元素个数>=k,说明第 k 大的数在右边区域
        if (right - r + 1 >= k) {
            return Top_k(nums, r, right, k);
        }
        // 若右边区域个数<k,但中间加右边区域个数>=k,说明第 k 大的数在中间区域
        else if (right - l >= k) {
            return key;
        }
        // 若中间加右边区域个数<k,说明第 k 大的数在左边区域
        else {
            return Top_k(nums, left, l, k - (right - l));
        }
    }

    int findKthLargest(vector<int>& nums, int k) {
        srand(time(NULL));
        return Top_k(nums, 0, nums.size() - 1, k);
    }
};

流程解析

文章配图


46. 最小的 K 个数

题目描述

输入整数数组 stock 和整数 cnt,找出数组中最小的 cnt 个数。

LCR 159. 库存管理 III - 力扣(LeetCode)

核心思路

这道题同样可以用快速选择算法解决。相比堆排序(O(NlogK)),快速选择的平均复杂度更接近 O(N)。

逻辑上,我们依然采用三路划分。不同的是,我们要找的是最小的 K 个数,所以主要关注**左侧区域(小于基准)**的长度:

  1. 如果左侧区域长度 >= cnt,说明目标都在左边,递归处理左半部分。
  2. 如果左侧区域长度 < cnt,但左侧 + 中间 >= cnt,说明基准及其左侧已经覆盖了所有需要的数,直接结束。
  3. 否则,需要在右侧继续寻找剩余的数。

C++ 代码实现

class Solution {
public:
    vector<int> inventoryManagement(vector<int>& stock, int cnt) {
        // 解法一:快排 (优点:简单无脑;缺点:时间复杂度很大 O(NlogN))
        // sort(stock.begin(), stock.end());
        // vector<int> ret;
        // for(int i = 0; i < cnt; i++) { ret.push_back(stock[i]); }
        // return ret;

        // 解法二:堆排序 (优点:时间复杂度比快排小 O(NlogK);缺点:比较难想)
        // vector<int> ret;
        // if(cnt == 0) return {};
        // priority_queue<int> pq(stock.begin(), stock.begin() + cnt);
        // for(int i = cnt; i < stock.size(); i++) {
        //     if(pq.top() > stock[i]) { pq.pop(); pq.push(stock[i]); }
        // }
        // while(!pq.empty()) { ret.push_back(pq.top()); pq.pop(); }
        // return ret;

        // 解法三:快速选择排序 (优点:时间复杂度非常小 逼近 O(N);缺点:方法很巧妙很难想到)
        if (cnt == 0) {
            return {};
        }
        srand(time(NULL));
        Top_k(stock, 0, stock.size() - 1, cnt);
        return vector<int>(stock.begin(), stock.begin() + cnt);
    }

    void Top_k(vector<int>& nums, int left, int right, int cnt) {
        if (left == right) {
            return;
        }
        
        int key = nums[rand() % (right - left + 1) + left];
        int l = left - 1, r = right + 1, i = left;
        
        while (i < r) {
            if (nums[i] > key) {
                swap(nums[i], nums[--r]);
            } else if (nums[i] < key) {
                swap(nums[i++], nums[++l]);
            } else {
                i++;
            }
        }
        
        if (l - left + 1 >= cnt) {
            return Top_k(nums, left, l, cnt);
        } else if (r - left >= cnt) {
            return;
        } else {
            return Top_k(nums, r, right, cnt - (r - left));
        }
    }
};

流程解析

文章配图


总结

这两道题的核心都在于快速选择算法。通过随机选取基准值并进行三路划分,我们可以在不完成全排序的情况下,根据区间长度直接定位目标范围。

相比于传统的全排序(O(NlogN))或堆排序(O(NlogK)),这种方法在平均情况下能将时间复杂度优化至 O(N)。虽然代码逻辑稍显复杂,需要仔细处理边界条件和递归终止条件,但在处理大规模数据查找时,性能优势非常明显。希望大家能掌握这种分治优化的思路。

目录

  1. 算法实战:快速选择解决第 K 大元素与最小 K 个数问题
  2. 45. 数组中的第 K 个最大元素
  3. 题目描述
  4. 核心思路
  5. C++ 代码实现
  6. 流程解析
  7. 46. 最小的 K 个数
  8. 题目描述
  9. 核心思路
  10. C++ 代码实现
  11. 流程解析
  12. 总结

更多推荐文章

查看全部
  • JavaScript 基础:鼠标事件中的修饰键与相关元素
  • agency-agents 深度解析:打造专属 AI 开发团队
  • 基于 AI 辅助的在线考试系统全流程开发实战
  • ControlNet-sd21 的入门与实战思路
  • 数据可信:AI+Data 时代企业落地的生死线与破局之道
  • Java 9 至 Java 25 语言演进与核心技术革新解析
  • OpenClaw 记忆系统实战:Token 压缩与双层记忆架构
  • 工作四年被辞退:程序员的职业反思与学习复盘
  • 实战:Protobuf 对象序列化在多模块依赖中的反序列化陷阱
  • AI 大模型简介与开发环境配置指南
  • C++ 基础进阶:内存管理、类型转换与 IO 流使用
  • Python 3.14 安装教程
  • Python 3.8.8 环境下离线安装 python-docx 的完整方案
  • Python Web UI 自动化测试:推送本地代码到 Git 远程仓库
  • Bugku Web 实战:PHP assert 绕过与文件读取
  • Python 爬虫实战:金融机构贷款产品信息抓取
  • SpringBoot 源码解析:AnnotationConfigServletWebServerApplicationContext 构造流程
  • 路径类动态规划入门:3 道经典例题详解(最小路径和、迷雾森林、过河卒)
  • OpenClaw 多智能体路由实战:飞书多机器人配置指南
  • Dexie 操作前端 IndexedDB 数据库教程

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online