Leetcode--Java--786. 第 K 个最小的素数分数

题目描述

给你一个按递增顺序排序的数组 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] 。

样例描述

示例 1:

输入:arr = [1,2,3,5], k = 3
输出:[2,5]
解释:已构造好的分数,排序后如下所示: 
1/5, 1/3, 2/5, 1/2, 3/5, 2/3
很明显第三个最小的分数是 2/5
示例 2:

输入:arr = [1,7], k = 1
输出:[1,7]

思路

方法一:浮点数二分 + 双指针 O(nlogn)
思路:由数组递增可知道, (i, j)对应的分数必然落在 [0, 1]范围内。因此二分判断满足小于等于它的数的个数大于等于k的最小数位置,利用双指针来求小于等于mid的数的个数
在这里插入图片描述
方法二: 优先队列(最大堆)
思路:通过扫描所有点对,然后加入优先队列的方式。

  1. 建立一个大根堆(保留堆中最大元素在堆顶),如果堆中元素少于k,直接加入堆中。如果元素达到k个,比较当前待插入元素与堆顶元素的大小关系,如果当前元素大,那一定不是第k小个元素,直接丢弃,如果当前元素小,则替换掉堆顶元素。
  2. 最后堆顶元素留下的就是第k小个元素。
  3. 优先队列的比较直接键函数。

方法三:多路归并 + 优先队列(最小堆)
思路:在方法二的基础上,结合数组本身的单调递增的性质,转化为多路序列求最小值的问题。
在这里插入图片描述

  1. 将所有路序列的第一个元素(也即是最小)加入到优先队列(最小堆)中。
  2. 循环k次,每次弹出的都是最小的元素,所以第k次就是第k小的数。弹出后,注意判断该序列路后还有没有数(i + 1 < j)有的话就加入。
  3. 注意:与方法二不同的是,这里堆中存的是下标不是数,因为下标更方便判断某路序列是否还有下一个元素

代码

方法一:浮点数二分 + 双指针 O(nlogn)

class Solution {
    double eps = 1e-8;
    int A, B;
    //双指针求mid前的树的个数
    public int get(int[] arr, double mid) {
          int n = arr.length;
          //记录mid左边数的个数
          int res = 0;
          //枚举分母
          for (int i = 0, j = 0; i < n; i ++ ) {
            
               //枚举分子可能的位置,这里用j + 1试探,满足才走过去
               while (j < n && (double)arr[j + 1] / arr[i] <= mid) j ++;
               //上面的范围是[0, j] 总共j + 1个数
               
               //注意判断是不越界时
               if ((double)arr[j] / arr[i] <= mid) res += j + 1;

               //如果已经求出答案,计算出A,B
               if (Math.abs((double)arr[j] / arr[i] - mid) < eps) 
               {
                   A = arr[j];
                   B = arr[i];
               }
          }
          return res;
    }
    public int[] kthSmallestPrimeFraction(int[] arr, int k) {
         double l = 0, r = 1;
         while (r - l > eps) {
             double mid = (l + r) / 2;
             //找到满足mid前数的个数满足 大于等于k 的最小数位置
             if (get(arr, mid) >= k) {
                r = mid;
             }
             else l = mid;
         }
         //此时r就是答案,在调用一次求A,B
         get(arr, l);
         return new int[]{A, B};
    }
}

方法二: 优先队列(堆)O(n^2log(n))


class Solution {
    public int[] kthSmallestPrimeFraction(int[] arr, int k) {
          int n = arr.length;
          //元素为有序对,就是数组   设置降序排列
          PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> Double.compare(1.0 * b[0] / b[1], 1.0 * a[0] / a[1]));
          for (int i = 0; i < n; i ++ ) {
              for(int j = i + 1; j < n; j ++ ) {
                  double cur = 1.0 * arr[i] / arr[j];
                  if (pq.size() < k) {
                      pq.add(new int[]{arr[i], arr[j]});
                  } else if (pq.size() == k){
                      //只有当前值比堆顶小,丢弃堆顶元素再加入当前,否则直接丢弃
                      if (cur < 1.0 * pq.peek()[0] / pq.peek()[1]) {
                          pq.poll();
                        pq.add(new int[]{arr[i], arr[j]});
                      }
                  } 
              }
          }
          return pq.poll();
    }
}

方法三:多路归并 + 优先队列

class Solution {
    public int[] kthSmallestPrimeFraction(int[] arr, int k) {
        int n = arr.length;
        //优先队列,最小堆 (存的是下标)
        PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> {
            double x = 1.0 * arr[a[0]] / arr[a[1]], y = 1.0 * arr[b[0]] / arr[b[1]];
            //Double的比较函数
            return Double.compare(x, y);
        });
        //先将所有路序列的最小元素加入堆
        for (int j = 1; j < n; j ++ ) {
            //多路序列arr[0]~arr[j] j属于0~n - 1
             pq.add(new int[]{0, j});
        }
        //先弹k - 1次
        while (k > 1 ) {
            int num[] = pq.poll();
            int i = num[0], j = num[1];
            k --;
            //如果该路序列后面还有数,就加入
            if (i + 1 < j) {
                pq.add(new int[]{i + 1, j});
            }    
        }
        //弹第k次,拿到答案
        int poll[] = pq.poll();
        return new int[]{arr[poll[0]], arr[poll[1]]};
          
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值