zoukankan      html  css  js  c++  java
  • [LeetCode 786.] K-th Smallest Prime Fraction

    LeetCode 786. K-th Smallest Prime Fraction

    一道经典题,给出两种经典解法。

    题目描述

    You are given a sorted integer array arr containing 1 and prime numbers, where all the integers of arr are unique. You are also given an integer k.

    For every i and j where 0 <= i < j < arr.length, we consider the fraction arr[i] / arr[j].

    Return the kth smallest fraction considered. Return your answer as an array of integers of size 2, where answer[0] == arr[i] and answer[1] == arr[j].

    Example 1:

    Input: arr = [1,2,3,5], k = 3
    Output: [2,5]
    Explanation: The fractions to be considered in sorted order are:
    1/5, 1/3, 2/5, 1/2, 3/5, and 2/3.
    The third fraction is 2/5.

    Example 2:

    Input: arr = [1,7], k = 1
    Output: [1,7]

    Constraints:

    • 2 <= arr.length <= 1000
    • 1 <= arr[i] <= 3 * 104
    • arr[0] == 1
    • arr[i] is a prime number for i > 0.
    • All the numbers of arr are unique and sorted in strictly increasing order.
    • 1 <= k <= arr.length * (arr.length - 1) / 2

    解题思路

    第一种解法是使用堆,类似 LeetCode 373. Find K Pairs with Smallest Sums,需要自定义 comparator,参考前面的【priority_queue 自定义 comparator】这种写法,重点在于会自定义 comparator。
    这俩有一个重点优化在于我们不需要把所有合法分数入队一遍,只需要根据分数的有序性质,入队头,然后按多路排序的思路每次出队再把自己的后继入队即可。
    堆解法的空间复杂度 O(K),时间复杂度 O(N+K+K*logK)

    第二种解法是使用二分查找,可能不是那么直观就能想到。首先分数的取值范围是 [0, 1],我们用浮点数来搜索一个可能的上限值,使其满足比这个值小的分数恰好是 k 个。在统计比浮点数小的值的过程中,同时找出一个比浮点数小但距离最近的分数,这个分数就是所求。
    二分查找空间复杂度 O(1),时间复杂度是多少?每一轮确定搜索区间后的统计阶段,时间复杂度是 O(N*N),那么搜索区间最多有几轮?

    O(log1.0) 显然是错误的。要注意,这并不是一个整数区间,所以并不能认为是进行了O(区间长)的轮数。
    我们注意到,我们搜索的 [l, r] 然后统计小于 m 的分数个数,本质上m的取值是在两个候选分数之间的“空隙”中,而每个“空隙”实际上最多进一次,空隙个数本质上看的是候选分数个数,所以轮数是 O(log(N*N))=O(logN),总体时间复杂度 O(N*N*logN)

    参考代码

    堆自定义 comparator 的解法

    /*
     * @lc app=leetcode id=786 lang=cpp
     *
     * [786] K-th Smallest Prime Fraction
     */
    
    // @lc code=start
    class Solution {
    public:
        vector<int> kthSmallestPrimeFraction(vector<int>& arr, int k) {
            using PII = pair<int,int>;
            auto cmp = [&](const PII& a, const PII& b) {
                auto [i1, j1] = a;
                auto [i2, j2] = b;
                return (double)arr[i1]/arr[j1] > (double)arr[i2]/arr[j2];
            };
            priority_queue<PII, deque<PII>, decltype(cmp)> q(cmp);
            int n = arr.size();
            for (int j=n-1; j>=1 && n-j<=k; j--) {
                q.emplace(0, j);
            }
            while (--k) {
                auto [i, j] = q.top();
                q.pop();
                // if (i+1 < n) {
                if (i+1 < j) {
                    q.emplace(i+1, j);
                }
            }
            auto [i, j] = q.top();
            return {arr[i], arr[j]};
        } // AC
    };
    // @lc code=end
    

    二分查找的解法

    这里其实还可以进一步优化,比如统计 cnt 的时候每个分子对应的一系列分数是有序的,遇到临界值就可以跳出循环了。更进一步,搜索临界值也不必线性扫描,二分查找就可以,每一轮统计只需要 O(N*logN) 时间就可以了。

    /*
     * @lc app=leetcode id=786 lang=cpp
     *
     * [786] K-th Smallest Prime Fraction
     */
    
    // @lc code=start
    class Solution {
    public:
        vector<int> kthSmallestPrimeFraction(vector<int>& arr, int k) {
            int n = arr.size();
            int p, q;
            double l = 0.0, r = 1.0;
            while (l < r) {
                double m = l + (r - l) / 2.0;
                int cnt = 0;
                double res = 0.0;
                for (int i=0; i<n; i++) {
                    for (int j=i+1; j<n; j++) {
                        double f = (double)arr[i] / arr[j];
                        if (f < m) {
                            cnt ++;
                            if (res < f) {
                                res = f;
                                p = arr[i];
                                q = arr[j];
                            }
                        }
                    }
                }
                if (cnt == k) break;
                else if (cnt < k) l = m;
                else r = m;
            }
            return {p, q};
        } // AC, vital
    };
    // @lc code=end
    
  • 相关阅读:
    uc浏览器开发版
    探索.NET中的事件机制
    “多态枚举”数值如何判断?
    关于“程序集与命名空间”
    AutoResetEvent和ManualResetEvent的异同
    C# 获取DOS命令的返回值
    自定义控件——自绘
    关于using……的一些探讨
    XmlDocument操作xml类
    使用Trigger实现Cascading的功能
  • 原文地址:https://www.cnblogs.com/zhcpku/p/14600369.html
Copyright © 2011-2022 走看看