LeetCode 786.第k个最小的素数分数
原题链接:LeetCode 786. 第k个最小的素数分数 Hard
算法:二分 + 搜索
题意:
给你一个按递增顺序排序的数组 arr
和一个整数k
。数组 arr
由 1
和若干 素数 组成,且其中所有整数互不相同。
对于每对满足 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;
}