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

数组TOP-K问题:求前K个最小元素的多种解法与C++实现

目录

引言

问题定义

方法一:排序法(最直观)

思路

方法二:堆(优先队列)法

思路

复杂度分析:

方法三:快速选择法(Quick Select)

方法四:计数排序法(数据范围有限时)

方法对比与选择建议

总结


引言

在算法面试和日常开发中,TOP-K问题是一类非常经典的问题。今天我们来探讨其中一个变体:给定一个无序数组,找出其中最小的K个元素。这个问题看似简单,实则蕴含着丰富的算法思想,我们将从暴力解法到最优解,逐一剖析并给出C++实现。

问题定义

输入:无序数组 arr,整数 K (1 ≤ K ≤ arr.length) 输出:数组中最小的K个元素(顺序不限) 示例:arr = [3,2,1,5,6,4], K = 2 → 输出 [1,2] 或 [2,1]

方法一:排序法(最直观)

思路

最简单的思路:将数组排序,然后取前K个元素。这种方法虽然时间复杂度较高,但代码简单,适合K接近N或对性能要求不高的场景。

复杂度分析

时间复杂度:O(N log N),主要来自排序

空间复杂度:O(1)(原地排序)或 O(N)(使用额外空间)

C++代码实现:

#include <vector> #include <algorithm> using namespace std; vector<int> topKBySort(vector<int>& nums, int k) { if (k >= nums.size()) return nums; sort(nums.begin(), nums.end()); return vector<int>(nums.begin(), nums.begin() + k); }

方法二:堆(优先队列)法

思路

维护一个大小为K的最大堆:

1). 遍历数组元素

2). 当堆的大小小于K时,直接入堆

3). 当堆的大小等于K时,如果当前元素小于堆顶(堆中最大元素),则弹出堆顶,将当前元素入堆

遍历结束后,堆中保存的就是最小的K个元素。

为什么用最大堆而不是最小堆?因为我们需要快速访问当前K个候选元素中的最大值,以便判断新元素是否需要加入。

复杂度分析:

时间复杂度:O(N log K),每个元素都可能进行堆操作

空间复杂度:O(K),堆的存储空间

C++代码实现:

#include <vector> #include <queue> using namespace std; vector<int> topKByHeap(vector<int>& nums, int k) { if (k >= nums.size()) return nums; if (k <= 0) return {}; // 使用最大堆(优先队列默认是最大堆) priority_queue<int> maxHeap; for (int num : nums) { if (maxHeap.size() < k) { maxHeap.push(num); } else if (num < maxHeap.top()) { maxHeap.pop(); maxHeap.push(num); } } // 将堆中的元素取出 vector<int> result; while (!maxHeap.empty()) { result.push_back(maxHeap.top()); maxHeap.pop(); } return result; }

方法三:快速选择法(Quick Select)

思路

基于快速排序的分区思想:

1). 选择一个基准元素(pivot)

2). 将数组分区,使得左边元素都小于等于pivot,右边都大于pivot

3). 如果pivot的位置恰好是K-1,则左边0到K-1就是前K小元素

4). 如果pivot的位置大于K-1,则在左边继续查找

5). 如果pivot的位置小于K-1,则在右边继续查找

这种方法能在平均情况下达到线性时间复杂度。

复杂度分析

时间复杂度:平均O(N),最坏O(N²)(可通过随机化pivot优化)

空间复杂度:O(1)(原地操作,不考虑递归栈)

C++代码实现:

#include <vector> #include <cstdlib> #include <ctime> using namespace std; class QuickSelect { private: int partition(vector<int>& nums, int left, int right) { // 随机选择pivot,避免最坏情况 int randomIndex = left + rand() % (right - left + 1); swap(nums[randomIndex], nums[right]); int pivot = nums[right]; int i = left; // i指向小于等于pivot的区域边界 for (int j = left; j < right; j++) { if (nums[j] <= pivot) { swap(nums[i], nums[j]); i++; } } swap(nums[i], nums[right]); return i; // 返回pivot的最终位置 } void quickSelect(vector<int>& nums, int left, int right, int k) { if (left >= right) return; int pivotIndex = partition(nums, left, right); if (pivotIndex == k) { return; } else if (pivotIndex > k) { quickSelect(nums, left, pivotIndex - 1, k); } else { quickSelect(nums, pivotIndex + 1, right, k); } } public: vector<int> topKByQuickSelect(vector<int> nums, int k) { if (k >= nums.size()) return nums; if (k <= 0) return {}; srand(time(nullptr)); quickSelect(nums, 0, nums.size() - 1, k - 1); // 前k个元素就是最小的k个(但未排序) return vector<int>(nums.begin(), nums.begin() + k); } };

方法四:计数排序法(数据范围有限时)

思路

当数组元素的范围较小且已知时,可以使用计数排序的思想:

1). 统计每个元素出现的频率

2). 从小到大遍历可能的元素值,累加计数直到达到K个

这种方法在某些特定场景下效率极高。

复杂度分析

时间复杂度:O(N + range),range为元素取值范围大小

空间复杂度:O(range)

C++代码实现:

#include <vector> #include <algorithm> using namespace std; vector<int> topKByCounting(vector<int>& nums, int k) { if (k >= nums.size()) return nums; if (k <= 0) return {}; // 假设元素范围在[0, 10000]之间 int maxVal = *max_element(nums.begin(), nums.end()); int minVal = *min_element(nums.begin(), nums.end()); vector<int> count(maxVal - minVal + 1, 0); // 统计频率 for (int num : nums) { count[num - minVal]++; } // 收集结果 vector<int> result; for (int i = 0; i < count.size(); i++) { while (count[i] > 0 && result.size() < k) { result.push_back(i + minVal); count[i]--; } if (result.size() == k) break; } return result; }

方法对比与选择建议

方法时间复杂度空间复杂度适用场景
排序法O(N log N)O(1)K接近N,代码简单优先
堆方法O(N log K)O(K)N很大,K较小
快速选择平均O(N)O(1)N很大,需要高效且不要求有序
计数排序O(N+range)O(range)数据范围小,元素分布集中

总结

TOP-K问题虽然基础,但涵盖了排序、堆、分治等多种算法思想。在实际应用中,我们应该根据数据规模和特点选择合适的方法:

  • 如果K接近N,排序法简单直接
  • 如果数据量巨大且K较小,堆方法是工业界最常用的方案
  • 如果追求极致性能且能接受最坏情况,快速选择是不错的选择
  • 如果数据范围有限,计数排序可能带来惊喜

理解这些方法不仅能解决TOP-K问题,更能帮助我们建立算法思维,应对更复杂的场景。

http://www.jsqmd.com/news/433221/

相关文章:

  • 鸿蒙系统开发工程师:深入解析技术栈与面试指南
  • 新疆大量元素水溶肥哪家好? - 品牌企业推荐师(官方)
  • 【vllm】DP 负载均衡
  • 华为鸿蒙开发指南:从基础到实战与面试准备
  • 问舟科技GEO AI搜索优化 开启AI搜索营销新时代 - 品牌企业推荐师(官方)
  • 鸿蒙开发深度解析:从核心技术到实战面试全攻略
  • ​2026年自动门风淋室厂家选购综合评测与厂家推荐:5家实力工厂+6步避坑 - 品牌企业推荐师(官方)
  • 【vllm】spawn
  • HDFS元数据大小优化:小文件合并+元数据精简技巧
  • 吨袋集装袋编织袋采购必看!吨袋实力厂家精选推荐,选购攻略一文吃透 - 品牌企业推荐师(官方)
  • 【socket] 发布与订阅
  • KISSABC官方购买与服务指南 - 品牌企业推荐师(官方)
  • Linux 6.19 内核发布:开发者活跃度创纪录,谁在驱动这台全球最大的开源引擎?
  • 豆包多行业广告推广方案,豆包AI服务商联系方式 - 品牌2026
  • word公式编辑
  • Linux 内核 7.0 撤回重磅补丁:一场关于 Rust 模式、C 语言限制与“瞬态设备”的社区大论战
  • N340迪可橡皮布定制评测:2026年服务与性价比考察,蓝色溶剂墨盒/半寸墨盒/427迪可橡皮布,迪可橡皮布厂商口碑排行 - 品牌推荐师
  • mysql核心知识清单
  • AI Agent在智能浴缸中的水疗养生定制系统
  • 2026城固装修公司排名权威测评|城固哪家装修公司靠谱?高性价比透明装修首选金匠装饰 - 一个呆呆
  • FAST-LIVO2 快速总结
  • 9oz线路板评测 哪家厚铜板不发热
  • pcb盲埋孔厂家排名 树脂塞孔工艺评测
  • 2026年耐候胶五大厂家排名及解析 - 十大品牌榜
  • 数据挖掘在大数据领域的风险管理应用
  • 透明PCB打样评测 哪家工艺最值得选
  • PCB金手指工艺揭秘 为何插拔万次仍接触良好
  • 高频混压HDI排行榜,2026最新评测
  • LoRA微调:用0.1%参数成本,让大模型秒变领域专家!中小企业必备AI降本秘籍!
  • 大模型保姆级学习路线+避坑指南,非常详细!小白转行大模型,年薪70W+!