題目描述:給定一個升序陣列 arr,其中包含 1 和若干質數。考慮所有可能的分數 arr[i] / arr[j](其中 i < j),回傳第 k 小的分數,結果以長度為 2 的整數陣列 [arr[i], arr[j]] 表示。
解題思路:
這道題的核心在於如何在不枚舉所有 O(n²) 個分數的情況下,高效地找到第 k 小的分數。
最小堆(Min-Heap)策略:
關鍵觀察:對於固定的分子 arr[i],隨著分母從大到小,分數值遞增。換言之,arr[i] / arr[n-1] 是以 arr[i] 為分子的所有分數中最小的。
初始化時,將所有 n-1 個「最小候選分數」放入最小堆:
arr[0] / arr[n-1]arr[1] / arr[n-1]arr[n-2] / arr[n-1]每次從堆頂彈出當前最小分數 arr[i] / arr[j],這是第幾次彈出就是第幾小的分數。彈出後,下一個以 arr[i] 為分子的候選者是 arr[i] / arr[j-1](縮小分母,分數增大),若 j - 1 > i(保證 i < j)則將其加入堆中。
重複彈出 k 次,第 k 次彈出的就是答案。
堆元素設計:堆中每個元素儲存 {分數值, 分子索引 i, 分母索引 j},以分數值作為比較鍵。
時間複雜度:O(k log n) — 初始化堆需 O(n log n),但 n ≤ k 時堆大小始終不超過 n,每次 push/pop 為 O(log n),共執行 k 次,故整體為 O(k log n)。
空間複雜度:O(n) — 最小堆中最多同時存放 n-1 個元素(每個分子索引對應一個當前候選分數)。
函數 kthSmallestPrimeFraction(arr, k):
n = arr 的長度
建立最小堆(以分數值排序)
步驟 1:初始化堆
對每個 i 從 0 到 n-2:
將 (arr[i] / arr[n-1], i, n-1) 加入最小堆
步驟 2:彈出 k-1 次
重複 k-1 次:
彈出堆頂 (value, i, j)
若 j-1 > i:
將 (arr[i] / arr[j-1], i, j-1) 加入最小堆
步驟 3:第 k 次彈出為答案
彈出堆頂 (value, i, j)
回傳 [arr[i], arr[j]]對分數值做二分搜尋,尋找最小的閾值 mid 使得小於等於 mid 的分數恰好有 k 個。
對每個固定分母 arr[j],用雙指針快速計算有多少個分子 arr[i] 使得 arr[i] / arr[j] <= mid。整體時間複雜度 O(n log(1/ε))(ε 為浮點精度),在競賽題中常見但有精度風險。
// 二分搜尋框架(示意)
double lo = 0, hi = 1;
while (hi - lo > 1e-9) {
double mid = (lo + hi) / 2;
// 計算 <= mid 的分數個數及最大分數
if (count >= k) hi = mid;
else lo = mid;
}
枚舉所有 O(n²) 對 (i, j) 計算分數,存入陣列後排序,取第 k 個。
時間複雜度 O(n² log n),空間 O(n²)。n ≤ 1000 時約 10⁶ 個元素,尚可接受,但不如堆方案優雅。
將問題視為對 n-1 個有序序列做 k 路合併。每個序列 i 為 arr[i]/arr[n-1] < arr[i]/arr[n-2] < ...。最小堆法本質上就是多路合併,時間 O(k log n)。
變形題:若陣列中的數不一定是質數,只是正整數(可能有重複),如何找第 k 小的分數?重複值會產生相等分數,需要在計數和去重邏輯上做調整。
二維矩陣第 k 小:LeetCode 378「有序矩陣中第 k 小的元素」與本題高度相似——矩陣每行每列均有序,同樣可用最小堆或二分搜尋解決,理解本題有助於掌握該類問題的通用框架。
K-th Smallest in Cartesian Product:若將問題推廣至兩個不同陣列 A 和 B,求所有 A[i] × B[j](或 A[i] + B[j])中第 k 小,同樣可用最小堆的多路合併思路,是分散式系統中 top-k 查詢的基礎。