一、在N个数中,寻找最大的K个数
这里只考虑K不等于1的情况,K = 1时,可以通过N - 1次比较和交换得到结果。
1. N不大的情况下,几千个左右。
① 先排序,快速排序或者堆排序,平均复杂度为O(N*log2N),再取出前K个,O(K)。总时间复杂度,O(N*log2N) + O(K) =O(N*log2N) ;
② 若K <= log2N,可以进行部分排序(选择排序和冒泡排序)。把N个数中的前K个数排序出来,复杂度是O(N*K);
③ 寻找N个数中最大的K个数,本质上就是寻找最大的K个数中最小的那个,也就是第K大的数。然后再遍历一次N个数,计算比第K大的数大的数的个数。
2. N很大的情况下,100亿
① 用容量为K的数组存放数组的前K个数。读入第K+1个数X,扫描数组,找到数组中的最小数Y,比较Y与X,如果X大于Y,用X代替Y。如果X小于等于Y,保持原数组不变。时间负责度O(N*K);
② 进一步,可以用小顶堆来存放着K个数。整个算法的时间负责度为O(Nlog2K);
if(x > h[0])
{
h[0] = x;
p = 0;
while(p < x)
{
q = 2 * p + 1;
if(q >= k)
break;
if((q < k - 1)&&(h[q + 1] < h[q]))
q = q + 1;
if(h[q] < h[p])
{
t = h[p];
h[p] = h[q];
h[q] = t;
p = q;
}
else
break;
}
}
③ 如果K仍然很大,可以尝试先找到最大的K’个元素,然后找第K’ + 1个到第2 * K’个元素,依次类推。(其中容量K’的堆可以放入内存)。这样,需要扫描所有数据ceil(K/K’)次。
二、在N个数中,寻找第K大的数
1. 可以使用二分策略。对于一个给定的数p,可以在O(N)内找到所有不小于p的数。假如N个数中最大数为Vmax,最小数为Vmin,那么第K大的数一定在区间[Vmax,Vmin]之间。可以在这个区间内二分搜索N个数中的第K大数p。
While(Vmax - Vmin > delta)
{
Vmid = Vmin + (Vmax - Vmin)*0.5;
if(f(arr,N,Vmid) >= K)
Vmin = Vmid;
else Vmax = Vmid;
}
f(arr,N,Vmid) 返回数组arr[0,N - 1]中大于等于Vmid的数的个数,delta要比N个数中,任意最小的两个数的差值小。时间复杂度为O(N*log2|Vmax - Vmin|/delta),时间复杂度跟数据分布有关。在数据分布均匀情况下,时间复杂度为O(N*log2N)。
JD 题目1534:数组中第K小的数字
#define Max_N 100005
#define Max_M 100005
typedef long long LL;
using namespace std;
int N,M;
int A[Max_M];
int B[Max_N];
LL K;
bool judge(LL x)
{
LL sum = 0;
for(int i = 1;i <= M;i++)
{
LL a = A[i];
sum += upper_bound(B+1 , B+1+N , x - a) - (B+1) ;
if(sum >= K) return true;
}
return sum >= K;
}
LL solve()
{
sort(A + 1,A + 1 + M);
sort(B + 1,B + 1 + N);
LL min_ = A[1] + B[1];
LL max_ = A[M] + B[N];
LL mid ,ans = 0;
while(min_ <= max_)
{
mid = (min_ + max_)>>1;
if(judge(mid))
{
ans = mid;
max_ = mid - 1;
}
else
min_ = mid + 1;
}
return ans;
}
int main()
{
while(cin >> M >> N >> K)
{
for(int i = 1;i <= M;i++)
scanf("%d",&A[i]);
for(int i = 1;i <= N;i++)
scanf("%d",&B[i]);
cout << solve() << endl;
}
return 0;
}
2. 如果N个数都是正整数,且它们的取值范围不太大,可以考虑申请空间,记录每个正数出现的次数,然后再从大到小取最大的K个。比如,所有正数在(0,MAXN)之间,利用一个数组count[MAXN]来记录每个正数出现的个数,count[i]表示正数i出现的个数。只要扫描一遍就可以得到count数组,然后寻找第K大的元素。
for(sumCount = 0, v = MAXN - 1;v >= 0;v--)
{
sumCount += count[v];
if(sumCount >= K)
break;
}
return v;
三、在N个数中,寻找第K到M大的数(0 < K <= M <= N)
先找出第K大p和第M大的数q,再扫描一遍,取出[p ,q]之间的数。