- 第 K 个最小的素数分数
一个已排序好的表 A,其包含 1 和其他一些素数. 当列表中的每一个 p<q 时,我们可以构造一个分数 p/q 。
那么第 k 个最小的分数是多少呢? 以整数数组的形式返回你的答案, 这里 answer[0] = p 且 answer[1] = q.
示例:
输入: A = [1, 2, 3, 5], K = 3
输出: [2, 5]
解释:
已构造好的分数,排序后如下所示:
1/5, 1/3, 2/5, 1/2, 3/5, 2/3.
很明显第三个最小的分数是 2/5.
输入: A = [1, 7], K = 1
输出: [1, 7]
注意:
A 长度的取值范围在 2 — 2000.
每个 A[i] 的值在 1 —30000.
K 取值范围为 1 —A.length * (A.length - 1) / 2
题解:
很有趣的题目吧,折腾了一天,一开始我的想法是先二分找到答案的分子再二分找答案的分母,但是你排序后会发现,分子分母的排序没有规律,所以这样不行,于是考虑到,都是小数,可以在区间[0,1.0]之间进行二分,其实我一开始也想到这样,但是对小数进行二分常常因为精度的问题发生错误,所以很害怕,好吧最后通过证明这个题目大概率就是二分小数找答案,但是过程很痛苦。
先说一个问题,如果给你一个分数a/b,你能知道这个分数是属于第几小的吗?很简单,从0开始把数组A遍历一遍,每次把A[i]作为分子,n=A.size()-1,然后在区间[i+1,n]之间二分找分母,找到第一个A[j],使得(A[i]/A[j])<=(a/b),那么分母再往后形成的分数越小,于是针对分子A[i],有(n-j+1)个数字小于(a/b),那么遍历一遍的结果总共有ans个数字小于等于(a/b),于是(a/b)是第ans小的数字,没有任何问题,那么考虑查询时间,为nlogn,但是每次都是在[i+1,n]二分,越往后区间[i+1,n]越小,所以时间复杂度远小于nlogn,好的,考虑好这个,我们开始对区间[0,1]二分,得到某个小数x,然后通过刚才说的方法找到小数x在A构成的分数中属于第几小,尽可能逼近K,然后呢,我们就得到了答案小数res,然后呢,再去A中二分找哪两个数字构成的分数很接近res,那么这两个数字就是答案,这样就很容易理解了吧,全程二分,从二分到二分。。。。。
PS:注意精度,一开始精度开小了,一直错,把精度开大点,也就是二分误差尽可能小一点。
AC代码
class Solution {
public:
int fun(double x,vector<int>& A)
{
int ans=0;
for(int i=0;i<A.size()-1;i++)
{
int l=i,r=A.size()-1,fin=-1;
while(l<=r)
{
int mid=(l+r)/2;
double a=double(A[i]);
double b=double(A[mid]);
if(a/b<=x)
{
fin=mid;
r=mid-1;
}
else l=mid+1;
}
if(fin!=-1)
ans+=(A.size()-fin);
}
return ans;
}
vector<int> kthSmallestPrimeFraction(vector<int>& A, int K) {
double l=0,r=1,res=-1;
while(abs(l-r)>=1e-8)
{
double mid=(l+r)/2;
int ans=fun(mid,A);
//cout<<ans<<endl;
if(ans>=K)
{
res=mid;
r=mid;
}
else l=mid;
}
double d=1e9;
int resA,resB;
for(int i=0;i<A.size()-1;i++)
{
int l=i,r=A.size()-1,fin=-1;
while(l<=r)
{
int mid=(l+r)/2;
double a=double(A[i]);
double b=double(A[mid]);
if(a/b>=res-1e-8)
{
fin=mid;
l=mid+1;
}
else r=mid-1;
}
if(fin!=-1)
{
double a=double(A[i]);
double b=double(A[fin]);
if(abs(a/b-res)<1e-8&&abs(a/b-res)<d)
{
d=abs(a/b-res);
resA=A[i];
resB=A[fin];
}
}
}
vector<int>re;
re.push_back(resA);
re.push_back(resB);
return re;
}
};