这算是一道相当经典的算法题了:

在长度为N的乱序数组中寻找第k(n>=k)大的元素。

(1)最简单直接的方法:先排序再找

最简单直接的想法是首先进行排序。假设元素的数量不大,比如才几千个,那就可以先进行排序,比如用快排或堆排,平均时间复杂度为O(N*logN),然后取出前k个,于是总时间复杂度为O(NlogN)+O(k)=O(NlogN)。当然这种做法是浪费了不少的时间的,因为题目只要求找出第k大的元素,而不需要数据是有序的。

(2)比较直接的方法:部分元素排序

当k比较小的时候,k趟排序是个比较不错的方法。我们只需要排序最大的k个元素即可,剩下那些元素不需要管。最简单明了的就是在冒泡排序中只进行k趟起泡:

#include<iostream>using namespace std;
int findMaxK(int a[], int n, int k) {//进行k趟起泡即可if (k == n) k = n - 1;bool flag;for (int i = 0; i < k; i++) {flag = false;for (int j = 0; j < n - i - 1; j++) {if (a[j] > a[j + 1]) {int tmp = a[j];a[j] = a[j + 1];a[j + 1] = tmp;if (!flag) flag = true;}}if (!flag) break;}return a[n - k];
}int main() {int A[] = { 5,1,2,2,3,4,3,10};int n=8,k =5;int result= findMaxK(A, n, k);cout << "第" << k << "大的数字为" << result << endl;system("pause");return 0;
}

用上述方法,时间复杂度为O(N*k),适用于k相对于N很小的情况。

(3)快排的分治法

快速排序使用了分治法的策略。它的基本思想是,选择一个基准数(一般称之为枢纽元),通过一趟排序将要排序的数据分割成独立的两部分:在枢纽元左边的所有元素都不比它大,右边所有元素都比它大,此时枢纽元就处在它应该在的正确位置上了。

在本问题中,假设有N个数存储在数组a中。我们从a中随机找出一个元素作为枢纽元,把数组分为两部分。其中左边元素都不比枢纽元大,右边元素都不比枢纽元小。此时枢纽元所在的位置记为mid。

如果右半边(包括a[mid])的长度恰好为k,说明a[mid]就是需要的第k大元素,直接返回a[mid]。

如果右半边(包括a[mid])的长度大于k,说明要寻找的第k大元素就在右半边,往右半边寻找。

如果右半边(包括a[mid])的长度小于k,说明要寻找的第k大元素就在左半边,往左半边寻找。

// 对快排算法简单改造了一下,返回本次的基准值下标
int quickSort(int a[], int left, int right) {if (a==NULL) {return -1;}if (left>right) {return -1;}int low = left;int high = right;// 选择基准数int temp = a[left];while (low<high) {while (low<high && a[high]>=temp) {high--;}while (low<high && a[low] <= temp) {low++;}if (low<high) {int t = a[low];a[low] = a[high];a[high] = t;}}// 到这里说明low==high, 交换基准数和相遇点的位置a[left] = a[low];a[low] = temp;return low;
}int findKth(int* a, int aLen, int n, int k ) {int mid = quickSort(a, 0, aLen-1);while (mid!=aLen-k) {if (mid>aLen-k) {mid = quickSort(a, 0, mid-1);}if (mid<aLen-k){mid = quickSort(a, mid+1, aLen-1);}}return a[mid];}

时间复杂度为O(N*logk)。当然,如果每次选择的枢纽元素都是最坏的那个,时间复杂度就会退化为O(N*k)。

(4)借助最小堆

前面的解法都可以认为是排序算法的变种,需要对原数组进行多次访问。如果原数组特别大(上百万,甚至上亿),以至于无法存放在内存中,前面的方法就不适用了(毕竟访问外存储器的代价太大)。如果k足够小(k个数据足以放入内存),可以借助二叉堆来完成。这种解法在剑指offer中有所提及,先建立一个规模为k的最小化堆,然后每次拿待处理的数组中元素和堆的最小元素(根结点元素值)比较。如果待插入元素大于根结点元素,则在堆中删除根结点,并把待处理的元素入堆。否则可以抛弃这个元素。

下面用STL标准库中的priority_queue来模拟这个过程:

#include<iostream>
#include<queue>using namespace std;struct cmp
{bool operator()(int &a, int &b) const{//因为优先出列判定为!cmp,所以反向定义实现最小值优先return a > b;}
};int findMaxK(int a[], int n, int k) {priority_queue<int,vector<int>,cmp> myqueue;for (int i = 0; i < n; i++) {if (myqueue.size() < k) {myqueue.push(a[i]);}else {//将最小元素与a[i]比较int min = myqueue.top();if (a[i] > min) {myqueue.pop();}myqueue.push(a[i]);}}return myqueue.top();
}int main() {int A[] = { 1,2,2,2,3,3,3 };int n=7,k = 7;int result= findMaxK(A, n, k);cout << "第" << k << "大的数字为" << result << endl;system("pause");return 0;
}

时间复杂度为O(N*logk),因为二叉堆的插入和删除操作都是logk的时间复杂度。

(5)键值索引法,桶排序

这个方法对原数组a有要求:所有 N 个数都是正整数,且它们的取值范围不太大(假设a的所有元素都位于区间[0,max])。此时可以申请一个规模为max+1的数组count,将count的全部元素初始化为0。然后利用count记录a中每个元素出现的个数。这样就可以在O(N)时间复杂度下找到a的第k大元素。

#include<iostream>
#include<vector>using namespace std;struct cmp
{bool operator()(int &a, int &b) const{//因为优先出列判定为!cmp,所以反向定义实现最小值优先return a > b;}
};int findMaxK(int a[], int n, int k,int max) {vector<int> count(max + 1);for (int i = 0; i < max; i++) {count[i] = 0;}for (int j = 0; j < n; j++) {count[a[j]]++;}int ind = max;while (k > 0) {if (count[ind] == 0) ind--;else {count[ind]--;k--;}}return ind;
}int main() {int A[] = { 5,1,2,2,3,4,3,10};int n=8,k = 6;int result= findMaxK(A, n, k, 10);cout << "第" << k << "大的数字为" << result << endl;system("pause");return 0;
} 

你可能会觉得,这种方法实在是好,只需O(N)的时间复杂度。但仔细想想,上面代码实现的键值索引法并不一定是O(N)的时间复杂度。确实,一开始构建count数组时,只需访问原数组a一次即可,时间为O(N),但count构建结束后,从count中找到第k大的元素却并不是O(N)时间。大家不妨考虑一种情况:如果max比N大得多呢?比如要找a={1,2,3,100,100}的第3大元素,首先构建一个长度为101的数组,得把数组的全部元素置为0,O(max)。然后用count统计a中每个整数出现的次数,O(N)。最后寻找第3大元素,cout的访问次数为接近100次。因此,这个方法实际的时间复杂度为O(max)。

结论:这种方法的时间复杂度为O(max),当max与N接近时,才能获得较好的性能。若max远大于N,就是个不好的方法。

寻找数组中第k大的元素相关推荐

  1. python第k序列元素查找_【python】寻找数组中第k大的元素

    题目链接:https://www.nowcoder.com/question/next?pid=13956292&qid=298692&tid=26431616 方法一: 最简单直接的 ...

  2. 经典算法题:寻找数组中第k大的元素

    这算是一道相当经典的算法题了: 在长度为N的乱序数组中寻找第k(n>=k)大的元素. 扩展思考:如何处理数组中的重复元素?比如,对于数组a={1,2,2,2,3,3,3},第二大的元素应该是3还 ...

  3. 寻找数组中第k大的数

    题目:有一个整数数组,请你根据快速排序的思路,找出数组中第K大的数. 给定一个整数数组a,同时给定它的大小n和要找的K(K在1到n之间),请返回第K大的数,保证答案存在. 测试样例: [1,3,5,2 ...

  4. 怎么修改数组中指定元素_求数组中第K大的元素

    问题描述 求无序数组int[] nums中第K大的元素. 例如 输入:nums[] = {9,5,8},k = 2 输出:8 输入:nums[] = {3,1,2,4,5,5,6},k = 4 输出: ...

  5. 算法--排序--寻找数组内第K大的元素

    此题目,需要用到快速排序里的划分数组操作: 快排参考:https://blog.csdn.net/qq_21201267/article/details/81516569#t2 先选取一个合适的哨兵( ...

  6. Leetcode215 寻找数组中第K大的数

    题目描述 给定一个数组A,要求找到数组A中第K大的数字. 思路:对于这种题目,其实有多种解法 方法1:对数组A进行排序,然后遍历一遍就可以找到第K大的数字.该方法的时间复杂度为O(N*logN) 方法 ...

  7. 寻找数组中第K大的数,时间复杂度O(N)

    给定一个数组A,要求找到数组A中第K大的数字.对于这个问题,解决方案有不少,此处我只给出三种: 有道题目寻找第K大,我用这3种方法都做了一遍. 方法1: 对数组A进行排序,然后遍历一遍就可以找到第K大 ...

  8. python查找第k大的数_寻找数组中第K大的数

    给定一个数组A,要求找到数组A中第K大的数字.对于这个问题,解决方案有不少,此处我只给出三种: 方法1: 对数组A进行排序,然后遍历一遍就可以找到第K大的数字.该方法的时间复杂度为O(N*logN) ...

  9. java查找第k大的数字_[经典算法题]寻找数组中第K大的数的方法总结

    今天看算法分析是,看到一个这样的问题,就是在一堆数据中查找到第k个大的值. 名称是:设计一组N个数,确定其中第k个最大值,这是一个选择问题,当然,解决这个问题的方法很多,本人在网上搜索了一番,查找到以 ...

最新文章

  1. 一种电子病历系统软件框架思想
  2. 虚拟dom_虚拟DOM发展的前世与今身
  3. IDEA下SVN基本使用
  4. 搜狗浏览器验证码无法显示_逃离塔科夫账号注册-验证码不显示
  5. linux文件类型缩写,常见Linux系统目录、文件类型、ls命令、alias命令
  6. flask部署pytorch模型
  7. IOS之导航控制器传值
  8. R语言各个包里面的数据集
  9. rgb矩阵Android程序,Android ColorMatrixColorFilter
  10. 一种基于地理信息的服务方式
  11. 去掉WPS智能生成目录中的空白行
  12. 腾达ac1200开虚拟服务器,腾达F1200 11AC双频无线路由器的上网设置教程
  13. docker logs 查看日志
  14. leetcode 思路——64. 最小路径和——174. 地下城游戏
  15. 南卡和三星哪款蓝牙耳机音质好些?高颜值且音质好的蓝牙耳机测评
  16. You probably need to get an updated matplotlibrc file from解决方法
  17. 【微信小程序】别踩白块源码免费分享
  18. 锂离子蓄电池充电方法
  19. 艰难的LinuxCNC(EMC2)源代码安装依赖01
  20. 第13届D2大会 - 参会感受和总结

热门文章

  1. Arduino通过PS2模块使用PS2键盘
  2. 《Mastering the game of Go with deep neural networks and tree search》解读
  3. 上海linux运维培训,在上海想学习Linux运维去哪里
  4. [免费专栏] Android安全之APK逆向入门介绍
  5. 搜狐员工遭遇工资补助诈骗 黑产与灰产有何区别 又要如何溯源?
  6. mysql中报了 tmp空间不足的问题,【案例】Oracle安装 检测阶段警告Free space: /tmp空间不足解决办法...
  7. LQB手打代码,DS1302基础代码01
  8. CDISC的ADaMIG (V1.2) 中英文对照【5】_第五章 附录
  9. 计算机网络原理总结 (英文版 第五版) Chapter2
  10. 一位增长黑客的创业史:Google挖掘细分市场,冷启动赚得百万美金