題目描述:給定一個 n×n 的整數矩陣,每一行與每一列都已升序排序。找出矩陣中第 k 小的元素(k 從 1 開始計數)。
解題思路(二分搜尋答案):
對「答案的值」進行二分搜尋,而非對索引二分。
[matrix[0][0], matrix[n-1][n-1]] 之間。upper_bound 找到第一個大於 mid 的位置,該位置的索引即為該行中 ≤ mid 的元素個數。所有行加總即為矩陣中 ≤ mid 的總數 count。count < k:mid 太小,所有 ≤ mid 的元素不足 k 個,故 left = mid + 1。count >= k):mid 可能是答案或比答案大,故 right = mid。left == right 時,left 即為第 k 小的值。此值一定存在於矩陣中,因為我們的收縮策略保證 right 始終是矩陣中實際出現的值。時間複雜度:O(n log(max - min)) — 二分搜尋值域範圍(max - min),每次需 O(n log n) 計算各行的 upper_bound(n 行,每行 O(log n)),故總體 O(n log n · log(max - min))。若值域範圍記為 V,則為 O(n log n · log V)。實際上可優化為 O(n log(max - min))(使用從左下角出發的計數法,每行 O(1) 均攤),但 upper_bound 解法更直觀。
空間複雜度:O(1) — 僅使用常數額外空間(不含輸入矩陣)。
Function kthSmallest(matrix, k):
n = number of rows (= number of columns)
left = matrix[0][0]
right = matrix[n-1][n-1]
While left < right:
mid = left + (right - left) / 2
count = 0
For each row in matrix:
count += number of elements <= mid in this row // use upper_bound
If count < k:
left = mid + 1 // answer is strictly greater than mid
Else:
right = mid // mid might be the answer or too large
Return left最小堆 O(k log n):初始將每行第一個元素加入最小堆(存 value、row、col)。每次彈出最小元素,若其右鄰元素存在則推入堆中,重複 k 次後彈出的即為答案。適合 k 遠小於 n² 的情形,但 k 接近 n² 時退化至 O(n² log n)。
左下角計數優化二分 O(n log(max - min)):計數時從矩陣左下角出發,向右走(元素 ≤ mid)或向上走(元素 > mid),每行只需 O(1) 時間,計數整體 O(n),使總複雜度降為 O(n log(max - min))。
排序展平 O(n² log n²):將矩陣展平成一維陣列後排序,直接取索引 k-1。程式碼最簡單,但破壞了矩陣的有序性,時間空間均需 O(n²),不推薦。
left 一定是矩陣中實際存在的值?請給出嚴格的數學證明。