总结 -- 寻找最大的K个数,寻找第K大的数

一、在N个数中,寻找最大的K个数

这里只考虑K不等于1的情况,K = 1时,可以通过N - 1次比较和交换得到结果。

 

1. N不大的情况下,几千个左右。

① 先排序,快速排序或者堆排序,平均复杂度为O(N*log2N),再取出前K个,OK)。总时间复杂度,O(N*log2N) + O(K) =O(N*log2N) ; 

 

② 若K <=  log2N,可以进行部分排序(选择排序和冒泡排序)。把N个数中的前K个数排序出来,复杂度是ON*K;

 

③ 寻找N个数中最大的K个数,本质上就是寻找最大的K个数中最小的那个,也就是第K大的数。然后再遍历一次N个数,计算比第K大的数大的数的个数。

 

2. N很大的情况下,100亿

① 用容量为K的数组存放数组的前K个数。读入第K+1个数X,扫描数组,找到数组中的最小数Y,比较YX,如果X大于Y,用X代替Y。如果X小于等于Y,保持原数组不变。时间负责度ON*K

② 进一步,可以用小顶堆来存放着K个数。整个算法的时间负责度为ONlog2K;


 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’的堆可以放入内存)。这样,需要扫描所有数据ceilK/K’)次。

 

二、在N个数中,寻找第K大的数

1. 可以使用二分策略。对于一个给定的数p,可以在ON)内找到所有不小于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个数中,任意最小的两个数的差值小。时间复杂度为ON*log2|Vmax - Vmin|/delta,时间复杂度跟数据分布有关。在数据分布均匀情况下,时间复杂度为ON*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个。比如,所有正数在(0MAXN)之间,利用一个数组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个数中,寻找第KM大的数(0 < K <= M <= N

先找出第Kp和第M大的数q,再扫描一遍,取出[p ,q]之间的数。

你可能感兴趣的:(数论,OJ)