LeetCode 786. 第 K 个最小的素数分数--从二分到二分

  1. 第 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;
    }
};

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值