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了,直接將這個候選值返回即可,否則如果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}; else if (cnt < K) left = mid; else right = mid; } } };
類似題目:
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/solution/
https://leetcode.com/problems/k-th-smallest-prime-fraction/discuss/115531/C++-9lines-priority-queue