題目描述:給定一組數對 pairs,其中 pairs[i] = [left, right](left < right)。若 b < c,則數對 (a, b) 可以接在 (c, d) 之前形成鏈。找出最長的鏈的長度。
解題思路:
這題等同於「區間排程最大化」(Interval Scheduling Maximization)問題,可以用貪心策略在 O(n log n) 時間內解決。
核心觀察:
演算法步驟:
right(結束值)升序排序所有數對。count = 1,curEnd = pairs[0][1](從第一個數對開始)。pairs[i][0] > curEnd(當前數對的開始值嚴格大於鏈的當前結尾),則可以接上這個數對,count++,並更新 curEnd = pairs[i][1]。count。為何貪心正確:假設存在最優解不選結束值最早的合法數對,我們可以將其中一個數對替換為結束值更早的那個,替換後鏈的長度不減少,且後續能接入的數對範圍只會更大或相同。因此貪心選擇不會使解變差。
範例說明:
pairs = [[1,2],[7,8],[4,5]]
排序後:[[1,2],[4,5],[7,8]]
curEnd = 2, count = 1
[4,5]:4 > 2 ✓ → count = 2, curEnd = 5
[7,8]:7 > 5 ✓ → count = 3, curEnd = 8
結果:3
時間複雜度:O(n log n) — 排序需要 O(n log n),後續單次線性遍歷為 O(n),整體由排序主導。
空間複雜度:O(1) — 僅使用常數個額外變數(排序為 in-place,不計遞迴棧空間)。
函式 findLongestChain(pairs):
1. 按 pairs[i][1](結束值)升序排序 pairs
2. 設 count = 1,curEnd = pairs[0][1]
3. 對 i 從 1 到 pairs.size() - 1:
a. 若 pairs[i][0] > curEnd:
- count 加 1
- curEnd 更新為 pairs[i][1]
4. 回傳 count按開始值排序後,定義 dp[i] 為以第 i 個數對結尾的最長鏈長度。
sort(pairs.begin(), pairs.end());
int n = pairs.size();
vector<int> dp(n, 1);
for (int i = 1; i < n; i++)
for (int j = 0; j < i; j++)
if (pairs[j][1] < pairs[i][0])
dp[i] = max(dp[i], dp[j] + 1);
return *max_element(dp.begin(), dp.end());
時間複雜度 O(n²),空間複雜度 O(n)。此方法思路清晰但效率低於貪心。
類比「最長遞增子序列(LIS)」的二分搜尋優化。對排序後的數對,維護一個「鏈的結尾值」陣列,用二分搜尋找到第一個結尾值 ≥ 當前開始值的位置並替換。時間複雜度 O(n log n),但實作比貪心複雜,且常數因子相同,不如貪心簡潔。
與方法二類似,對 DP 中的「找最大可接前驅」步驟用二分搜尋優化,將 O(n²) 降至 O(n log n),適合對 DP 解法有興趣的場景。
若限制鏈長度至少為 k,最多能形成幾條鏈? 貪心思路仍然適用:每次選出前 k 個最短結尾的數對形成一條鏈,但需要更複雜的分組策略。
Leetcode 435 — Non-overlapping Intervals(無重疊區間) 是本題的孿生題:要移除最少的區間使所有區間不重疊,等價於找最多的不重疊區間,即本題的貪心做法。請思考兩題的解法如何對應。
若數對可以重疊(例如 b == c 時也能接鏈),演算法需要如何修改? 只需將嚴格大於(>)改為大於等於(>=),貪心策略不變,但請思考這樣修改後正確性的證明是否需要調整。