面试题—— 找出一个无序整型数组中第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
(补发)
相关文章推荐
- 在一个无序整型数组中找出第k小的数字
- 给定一个无序整型数组,找出数组中未出现的最小整数
- [原]Java面试题-输入一个整型数组,找出最大值、最小值,并交换。
- 面试题———关于将一个整型数组中的所有元素组合成一个数字,并找出最小一个。
- 面试题:在一个数组中有0-99之间的整数101个(数组无序),用高效方法找出其中的唯一的重复元素!
- 找出一个整型数组中的元素的最大值
- 找出无序数组中第k小的数
- 面试题精选(79):取值为【1,n-1】含n个元素的整数数组至少存在一个重复数,O(n)时间内找出其中任意一个重复数
- 一个整型数组里除了两个数字之外,其他的数字都出现了两次,请写程序找出这两个只出现一次的数字
- 互联网面试题:一个数组中找出三个出现奇数次的数字中的一个
- 一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字
- 一个整型数组里除了两个数字之外,其他的数字都出现了两次。 请写程序找出这两个只出现一次的数字。
- 互联网面试题:一个数组中找出三个出现奇数次的数字中的一个
- 一个无序数组中第K大的元素
- 【算法题】找出一个整型数组里两个不同数字
- 【谷歌面试题】给出一个数组A,找出一对 (i, j)使得A[i] <= A[j] (i < j)并且j-i最大
- [面试题]用最少的比较次数找出一个数组中的最大值和次大值
- 一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字
- Java面试题:1-100之间的数,被放在数组a[99]中,有一个数没有包含在其中,用java代码找出这个数
- 一个整型数组里除了两个数字之外,其他的数字都出现了两次。 请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n), 空间复杂度是O(1)