A sorted list A
contains 1, plus some number of primes. Then, for every p < q in the list, we consider the fraction p/q.
What is the K
-th smallest fraction considered? Return your answer as an array of ints, where answer[0] = p
and answer[1] = q
.
Examples: Input: A = [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, 2/3. The third fraction is 2/5. Input: A = [1, 7], K = 1 Output: [1, 7]
Note:
A
will have length between2
and2000
.- Each
A[i]
will be between1
and30000
. K
will be between1
andA.length * (A.length - 1) / 2
.
這道題給了我們一個有序數組,里面是1和一些質數,說是對於任意兩個數,都可以組成一個 [0, 1] 之間分數,讓求第K小的分數是什么,題目中給的例子也很好的說明了題意。那么最直接暴力的解法就是遍歷出所有的分數,然后再進行排序,返回第K小的即可。但是這種無腦暴力搜索的方法 OJ 是不答應的,無奈,只能想其他的解法。由於數組是有序的,所以最小的分數肯定是由第一個數字和最后一個數字組成的,而接下來第二小的分數就不確定是由第二個數字和最后一個數字組成的,還是由第一個數字跟倒數第二個數字組成的。這里用一個最小堆來存分數,那么每次取的時候就可以將最小的分數取出來,由於前面說了,不能遍歷所有的分數都存入最小堆,那么該怎么辦呢,可以先存n個,哪n個呢?其實就是數組中的每個數字都和最后一個數字組成的分數。由於需要取出第K小的分數,那么在最小堆中取K個分數就可以了,第一個取出的分數就是那個由第一個數字和最后一個數字組成的最小的分數,然后就是精髓所在了,此時將分母所在的位置前移一位,還是和當前的分子組成新的分數,這里即為第一個數字和倒數第二個數字組成的分數,存入最小堆中,那么由於之前已經將第二個數字和倒數第一個數字組成的分數存入了最小堆,所以不用擔心第二小的分數不在堆中,這樣每取出一個分數,都新加一個稍稍比取出的大一點的分數,這樣取出了第K個分數即為所求,參見代碼如下:
解法一:
class Solution { public: vector<int> kthSmallestPrimeFraction(vector<int>& A, int K) { priority_queue<pair<double, pair<int, int>>> q; for (int i = 0; i < A.size(); ++i) { q.push({-1.0 * A[i] / A.back(), {i, A.size() - 1}}); } while (--K) { auto t = q.top().second; q.pop(); --t.second; q.push({-1.0 * A[t.first] / A[t.second], {t.first, t.second}}); } return {A[q.top().second.first], A[q.top().second.second]}; } };
其實這道題比較經典的解法是用二分搜索法 Binary Search,使用的二分搜索法是博主歸納總結帖 LeetCode Binary Search Summary 二分搜索法小結 中的第四種,即二分法的判定條件不是簡單的大小關系,而是可以抽離出子函數的情況,下面來看具體怎么弄。這種高級的二分搜索法在求第K小的數的時候經常使用,比如 Kth Smallest Element in a Sorted Matrix,Kth Smallest Number in Multiplication Table,和 Find K-th Smallest Pair Distance 等。思路都是用 mid 當作 candidate,然后統計小於 mid 的個數 cnt,和K進行比較,從而確定折半的方向。這道題也是如此,mid 為候選的分數值,剛開始時是 0.5,然后需要統計出不大於 mid 的分數都個數 cnt,同時也需要找出最接近 mid 的分數,當作返回的候選值,因為一旦 cnt 等於K了,直接將這個候選值返回即可,這個候選值分數是由p和q來表示的,其中p表示分子,初始化為0,q表示分母,初始化為1(因為除數不能為0),在內部的 while 循環退出時,分數 A[i]/A[j] 就是最接近 mid 的候選者,此時假如 p/q 要小於 A[i]/A[j],就要分別更新p和q。否則如果 cnt 小於K,說明應該增大一些 mid,將 left 賦值為 mid,反之如果 cnt 大於K,需要減小 mid,將 right 賦值為 mid,參見代碼如下:
解法二:
class Solution { public: vector<int> kthSmallestPrimeFraction(vector<int>& A, int K) { double left = 0, right = 1; int p = 0, q = 1, cnt = 0, n = A.size(); while (true) { double mid = left + (right - left) / 2.0; cnt = 0; p = 0; for (int i = 0, j = 0; i < n; ++i) { while (j < n && A[i] > mid * A[j]) ++j; cnt += n - j; if (j < n && p * A[j] < q * A[i]) { p = A[i]; q = A[j]; } } if (cnt == K) return {p, q}; if (cnt < K) left = mid; else right = mid; } } };
Github 同步地址:
https://github.com/grandyang/leetcode/issues/786
類似題目:
Find K Pairs with Smallest Sums
Kth Smallest Element in a Sorted Matrix
Kth Smallest Number in Multiplication Table
Find K-th Smallest Pair Distance
參考資料:
https://leetcode.com/problems/k-th-smallest-prime-fraction/
https://leetcode.com/problems/k-th-smallest-prime-fraction/discuss/115531/C++-9lines-priority-queue