LeetCode 786.第k个最小的素数分数

原题链接:LeetCode 786. 第k个最小的素数分数  Hard

算法:二分 + 搜索

题意

给你一个按递增顺序排序的数组 arr和一个整数k。数组 arr1和若干 素数 组成,且其中所有整数互不相同。

对于每对满足 0 < i < j < arr.length 的 i 和 j,可以得到分数 arr[i] / arr[j]

那么第 k个最小的分数是多少呢? 以长度为 2 的整数数组返回你的答案, 这里 answer[0] == arr[i] 且 answer[1] == arr[j]

关键点

我们假设第k个最小分数是t,那么小于t的分数个数必然是k - 1

由于所选的数组元素0 < arr[i]/arr[j] < 1, i < j,因此我们在区间(0,1)内进行二分,将每次所选得的mid值在集合{arr[i]/arr[j} | 0 <= i < j < arr.size()}中统计值小于mid的分数的个数cnt,同时我们记录值小于mid的分数中最大的那个分数的值以及其分子和分母,如果cnt < k,则说明mid比我们要找t小,我们调整二分区间为(mid, 1);如果cnt > k,则说明mid要比我们找的t大,我们调整二分区间为(0, mid)

抽象过程

上述过程中我们令初始的二分区间为l = 0; r = 1;如果cnt < k,调整二分区间l= mid;如果cnt > k,我们调整二分区间r = mid;如果cnt == k,我们返回我们记录的值小于mid的分数中最大的那个分数的值以及其分子和分母。

C代码

int a, b; // a记录最大值的分子,b记录其分母
double max; // max记录最大值
int check(int* arr, int size, double mid){
    max = 0;
    int res = 0;
    for(int i = 0 , j = 1; j < size; j++){
        while(arr[i] * 1.0 / arr[j] < mid) i++;
        
        res += i;
        for(int l = 0; l < i; l++){
            if(arr[l] * 1.0 / arr[j] > max){
                a = arr[l], b = arr[j], max = arr[l] * 1.0 / arr[j];
            }
        }
    }
    return res;
}
int* kthSmallestPrimeFraction(int* arr, int arrSize, int k, int* returnSize){
    max = 0;
    a = b = 0;
    double l = 0,  r = 1.0;

    while(1){
        double mid = (l + r) / 2;
        int cnt = check(arr, arrSize, mid); // 得到比mid小的数有多少个
        if(cnt < k) l = mid; 
        else if(cnt > k) r = mid; // 注意这里不能对l = mid + 1 或 r = mid - 1.因为mid是一个介于0到1之间的实数
        else break;
    }
    int* ans = (int*)malloc(sizeof(int) * 2);
    ans[0] = a, ans[1] = b;
    *returnSize = 2;
    return ans;
}