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
    
  • 相关阅读:
    Elementary Methods in Number Theory Exercise 1.3.13
    Elementary Methods in Number Theory Exercise 1.3.17, 1.3.18, 1.3.19, 1.3.20, 1.3.21
    数论概论(Joseph H.Silverman) 习题 5.3,Elementary methods in number theory exercise 1.3.23
    Elementary Methods in Number Theory Exercise 1.2.31
    数论概论(Joseph H.Silverman) 习题 5.3,Elementary methods in number theory exercise 1.3.23
    Elementary Methods in Number Theory Exercise 1.3.13
    Elementary Methods in Number Theory Exercise 1.3.17, 1.3.18, 1.3.19, 1.3.20, 1.3.21
    Elementary Methods in Number Theory Exercise 1.2.31
    Elementary Methods in Number Theory Exercise 1.2.26 The Heisenberg group
    4__面向对象的PHP之作用域
  • 原文地址:https://www.cnblogs.com/zhcpku/p/14600369.html
Copyright © 2011-2022 走看看