您的位置:首页 > 职场人生

面试题—— 找出一个无序整型数组中第k大的数。

2014-09-30 14:21 696 查看


题目简介:

经典面试题: 找出一个无序的整型数组中第k大的数。

函数接口如下:

int findKthLargestNum( int A[], int N,  int K)


解法一: 全排序 or 冒泡选择排序(K趟)

全排序: 

最简单的方法就是对整个数组排序(需要将整个数组装入内存),可以用快速排序和推排序。 (注意: 快排的平均时间复杂度为O(N log N),最坏为O(N^2); 堆排序的最坏和平均时间复杂度都是O(N* log N)。

总的时间复杂度为 O (N*log N) + O(K) = O(N*log N)

部分排序:

当K很小时,我们一般希望能够进行部分排序,这就利用到了 冒泡排序和选择排序的特性。 我们可以进行K趟 冒泡排序或选择排序, 就可以找到第K大的数。

最坏时间复杂度O(N*K),空间复杂度O(1)

关于 O(N*logN)的算法和O(N*K)算法那个好,关键取决于K与log N的大小,一般K<<N.


解法二: 快排排序思想:

快速排序的需要多次的partition,每一次partition把数据分成两部分;大于key的数放在一边,小于key的放在另一边。

现在我们利用快排思想来解决这个问题,我们每次partition将数据分为两部分,较小的数据Sl, 较大的数据Su,我们期望有一次partition可以得到正好Su.size()
== K; 或者经过几次partition,正好找到几次较大的数据为K个。

对于每一次partition,我们根据较大数据部分Su进行如下操作:

注意:

若Su的元素个数==K,遍历这K个数最小的即为所求; 

若Su的元素的个数 <K, 在Sl中进行partition,找K-Su.length个。

若Su的元素的个数 > K, 对数组中的大数部分进行partition

代码实现:

对于数组A,大小为N,找到K个最大的数

快排是原地排序,我们每次对于给定数组中的范围[start,end]进行partition,返回pivot在A数组中对应的pos。若pos == K-1,表示我们找到了第k个最大的数。 若pos > or < K-1, 则在相应的区域继续进行partition,知道一个pivot返回对应pos == K-1。  

详细代码如下:

#include <iostream>
#include <cstdio> //包含语言重定向函数freopen的库
#include <algorithm>
#include <utility>
#include <vector>
using namespace std;
void printArray(int a[], int n){
for (int i=0; i<n; ++i){
cout<<a[i]<<" ";
}
cout<<endl;
}
/**
partition2 :
大集 | pivot | 小集
*/
int partition2(int A[], int start, int end){
//swap(A[start],A[rand()%(end -start)];
int pivot = A[start];
int ix = start;
int jx = start + 1;
while (jx <= end){
if ( A[jx] > pivot ){ //控制大数在前
swap(A[jx], A[ix++]);
}
++jx;
}
A[ix] = pivot;
return ix;
}
/*
找出一个数组中第k大的数
*/
int findKthNum(int A[], int N, int K){
int start = 0;
int end = N-1;
int pos;
while ( true ){
pos = partition2(A, start , end);
if ( pos == K-1 ){
	break;
}else if (pos > K-1){
	end = pos-1; 
}else{
	start = pos + 1;
}
}
printArray(A,N);
return A[pos];
}
int *Create(int n){
int *p=new int[n];
int i=0;
for(; i < n; i++){
p[i]= rand()%10;
}
return p;
}
int main(){
int *A = NULL;
int n;
int k;
while(cin>>n){
cin>>k;
A = Create(n);
printArray(A, n);
cout<<findKthNum(A,n,k)<<endl;
}
return 0;
}



解法三: 采用二分搜索的思想:

寻找N个数中最大的K 个数,也可以找出最大K个数中的最小的那个(第Kth大的数),然后在遍历一次N个数选出比Kth数达的所有数,最后再加上Kth数就构成最大的K个数。

假如N个数中最大的数为Vmax,最小的数位Vmin,那么这个N个数中的K大的数一定在区间[Vmin, Vmax] 之间。那么,可以在 区间内二分搜索N个数中的第Kth大的数。二分法根据Vmid的值不断缩小区间[Vmin, Vmax], 最后知道[Vmin,
Vmax] 仅包含Kth Num;

思路挺好,

整个算法的时间复杂度为O(N*log2(|Vmax-Vmin|)但若N个数的大小是均匀分布的,则时间复杂度O(N*log_2^(N) )。

注意这个方法仅需要一遍又一遍的遍历整个数组集合,不需要做随机访问,若全部数据不能载入内存这个算法可以通过[Vmin, Vmax] 进行数据过滤(每次扫描找findLagerNumCun),待剩余的数据可以载入内存后,可进行内存排序,不必在通过读写文件的方式来操纵了(实际应用中,要考虑IO开销)。

详细代码如下:

#include <iostream>
#include <algorithm>
#include <utility>
using namespace std;
//打印数组
void printArray(int a[], int n){
for (int i=0; i<n; ++i){
cout<<a[i]<<" ";
}
cout<<endl;
}
//返回A数组中的数大于等于Vmid值的个数
int findLagerNumCun(int A[], int N, int Vmid) {
int cnt = 0;
for (int i = 0; i < N; ++i) {
if (A[i] >= Vmid) {
	++cnt;
}
}
return cnt;
}
//查找大小为N的数组A中,第K大的数,并返回
int findKthNum(int A[], int N, int K) {
if (N <= 0) {
cout << "error" << endl;
return -1;
}
int Vmin = A[0];
int Vmax = A[0];
for (int i=1; i < N; ++i) {
Vmin = min(Vmin, A[i]);
Vmax = max(Vmax, A[i]);
}
while (Vmin < Vmax) {
int Vmid = Vmin + (Vmax - Vmin)/2; 
if (Vmin == Vmid) { // Vmax == Vmin +1
	break; // 这说明了 大于等于Vmax的数小于K, 大于等于Vmin的数大于K, Vmin即为kth number
// 或者不break, 让 Vmax = Vmin
}
int cnt = findLagerNumCun(A, N, Vmid);
if (cnt > K) {
	Vmin = Vmid;
} else if (cnt < K){
	Vmax = Vmid;
} else {
	Vmin = Vmid;
	Vmax = Vmid;
}
}
return Vmin;
}
//随机创建一个大小为n的数组,元素1~9
int *create(int n){
int *p=new int[n];
int i=0;
for(; i < n; i++){
p[i] = rand()%10;
}
return p;
}
int main(){
int *A = NULL;
int n;
cout << "input the size of array A:   ";
while (cin >> n) {
A = create(n);
printArray(A, n);
int k;
cout << "input the kth:   ";
cin >> k;
int kth = findKthNum(A, n, k);
cout<< "kth = " << kth << endl;
sort(A, A+n); //排序一下,用来校验
printArray(A, n); //注意这里是升序,倒数第k个即为第k最大值
cout << "input the size of array A:   ";
}
return 0;
}



解法四: 堆排序

维持一个k个元素的最小堆。用一个优先队列即可。

时间复杂度O(N * log K)。

代码略。


解法五:计数排序(若N个数中的种类是已知的)

 寻找N个数中第K大的数,在理论上存在线性算法。不过这个线性算法的常数项比较大,在实际应用中效果有时也不太好。

若数组中的N个数都是[0,MAX) 之间的数(也就是数组A中数的范围类型都知道的),就可以用count[MAX]来记录每个整数出现的次数(count[i] 表示整数i在数组A中出现的次数)。我们只需扫描一遍数组就可以得到count数组,然后就可以找到第K大的数。

参考文献:http://blog.sina.com.cn/s/blog_54f82cc201013tke.html
(补发)

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  算法 topK 排序 面试题
相关文章推荐