題目描述:people[i] = [hi, ki],其中 hi 是第 i 個人的身高,ki 是在正確排列中,站在第 i 個人前面且身高大於或等於 hi 的人數。請重新排列使每個人的描述都成立。
解題思路:
排序 + 貪心插入是本題的經典解法,核心思路是「先放高個子」。
排序規則:
插入策略:對排序後的每個人 [h, k],直接插入到結果串列的第 k 個位置(0-based index)。
正確性分析:
當我們要插入 [h, k] 時:
[h, k] 而言,串列中所有已有的人都算作「前面身高 ≥ h 的人」之後插入的人身高都 ≤ h,他們不會影響 [h, k] 的 k 值(因為他們不算「身高 ≥ h」),所以位置永遠正確。
同身高副鍵的必要性:若兩人身高相同,假設 [h, k1] 和 [h, k2],k1 < k2,則先插入 k1 的人;k2 的人插在 k2 位置時,前面有 k1 處的同身高者,計數正確。若反向,先插 k2 的人再插 k1 會導致 k2 的人被往後推,k 值錯誤。
時間複雜度:O(n²) — 排序需 O(n log n);每次插入串列需移動迭代器 O(k) 步,最壞情況每次 O(n),共 n 次插入故為 O(n²)。整體瓶頸在插入,為 O(n²)。
空間複雜度:O(n) — 結果串列儲存 n 個人,使用 list 的額外空間也為 O(n)。若使用 vector 作為結果,每次 insert 需移動後續元素,時間複雜度同為 O(n²),但快取友好性更好,實際常數較小。
函數 reconstructQueue(people):
步驟 1:排序
對 people 排序,規則為:
身高降序;若身高相同,k 升序
步驟 2:依序插入
result = 空串列
對排序後每個 [h, k] 在 people 中:
將 [h, k] 插入 result 的第 k 個位置(index k)
步驟 3:回傳
回傳 result 轉成的陣列可以利用 Fenwick Tree 將每次「找到第 k 個空位」的操作從 O(n) 降至 O(log²n)(二分搜尋 + BIT 查詢),整體時間複雜度降至 O(n log²n)。
適合 n 很大(n > 10⁵)的場景,但本題 n ≤ 2000,O(n²) 完全夠用。
不使用 list,改用 vector<vector<int>> 直接呼叫 insert(begin() + k, p)。
vector<vector<int>> result;
for (const auto& p : people) {
result.insert(result.begin() + p[1], p);
}
return result;
時間複雜度同為 O(n²),但 vector 的連續記憶體使得快取命中率更高,在 n ≤ 2000 時實際速度往往比 list 快。
按身高升序排列,從 k 最大的人開始,利用「空位計數」逆向確定位置。此方法較難實作且不直觀,不推薦使用,但在理論上提供了另一個貪心方向的思考角度。
穩定性要求:若題目中允許多個合法答案,本貪心策略總是輸出字典序最小的結果嗎?如何證明或反駁?(提示:考慮同身高不同 k 值的排列)
三維版本:若每人有三個屬性 [hi, ki, wi](wi 為體重),且 ki 定義為前面身高且體重均 ≥ 的人數,問題難度如何變化?單一排序鍵不再足夠,需要多維排序或其他資料結構。
動態插入:若人員是動態到來的(不一次給全),每次插入一個人後需要維護當前最優排列,如何設計資料結構支援 O(log n) 的動態插入與查詢?這與 Order-Statistics Tree 的設計密切相關。