題目描述
這是 LeetCode 上的 646. 最長數對鍊 ,難度為 中等。
Tag : 「貪心」、「排序」、「二分」、「序列 DP」、「LIS」
給出
n
個數對。 在每一個數對中,第一個數字總是比第二個數字小。
現在,我們定義一種跟随關系,當且僅當
b < c
時,數對 才可以跟在
給定一個數對集合,找出能夠形成的最長數對鍊的長度。你不需要用到所有的數對,你可以以任何順序選擇其中的一些數對來構造。
示例:
輸入:[[1,2], [2,3], [3,4]]
輸出:2
解釋:最長的數對鍊是 [1,2] -> [3,4]
提示:
- 給出數對的個數在
排序 + 貪心 DP
起始先将
pairs
根據第一維排升序(或直接雙關鍵字排升序)。
考慮定義 為以 為結尾的最長數對鍊長度,所有
不失一般性考慮 該如何轉移:不難發現 為所有滿足「下标範圍在 ,且 」條件的
但實際上,我們隻需要從 開始往回找,找到第一個滿足 的位置
容易證明該做法的正确性:假設貪心解(該做法)找到的位置 不是最優位置,即存在比 更小的合法下标 滿足 。根據我們的排序規則必然有 的性質,則可知 必然可以代替 接在原本以 為結尾的最優數鍊上(最優數鍊長度不變,結果不會變差),則至少有 。
代碼:
class Solution {
public int findLongestChain(int[][] pairs) {
Arrays.sort(pairs, (a,b)->a[0]-b[0]);
int n = pairs.length, ans = 1;
int[] f = new int[n];
for (int i = 0; i < n; i++) {
f[i] = 1;
for (int j = i - 1; j >= 0 && f[i] == 1; j--) {
if (pairs[j][1] < pairs[i][0]) f[i] = f[j] + 1;
}
ans = Math.max(ans, f[i]);
}
return
- 時間複雜度:排序的複雜度為;不考慮剪枝效果
複雜度為。整體複雜度為DP
- 空間複雜度:
排序 + 貪心 DP(優化轉移)
根據上述分析,我們知道對于一個特定的 而言,其所有合法(滿足條件 )的前驅狀态
根據
LIS
問題的貪心解的思路,我們可以額外使用一個數組記錄下特定長度數鍊的最小結尾值,進而實作二分找前驅狀态。
具體的,建立 數組,其中 代表數鍊長度為 時結尾元素的第二維最小值為 。
如此一來,當我們要找 的前驅狀态時,等價于在 數組中找滿足「小于 」的最大下标。同時,我們不再需要顯式維護
不了解 LIS
問題的同學可以看前置 🧀 : LCS 問題與 LIS 問題的互相關系,以及 LIS 問題的最優解證明 🎉🎉🎉
代碼:
class Solution {
public int findLongestChain(int[][] pairs) {
Arrays.sort(pairs, (a,b)->a[0]-b[0]);
int n = pairs.length, ans = 1;
int[] g = new int[n + 10];
Arrays.fill(g, 0x3f3f3f3f);
for (int i = 0; i < n; i++) {
int l = 1, r = i + 1;
while (l < r) {
int mid = l + r >> 1;
if (g[mid] >= pairs[i][0]) r = mid;
else l = mid + 1;
}
g[r] = Math.min(g[r], pairs[i][1]);
ans = Math.max(ans, r);
}
return
- 時間複雜度:排序的複雜度為;
複雜度為。整體複雜度為DP
- 空間複雜度:
最後
這是我們「刷穿 LeetCode」系列文章的第
No.646
篇,系列開始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道題目,部分是有鎖題,我們将先把所有不帶鎖的題目刷完。
在這個系列文章裡面,除了講解解題思路以外,還會盡可能給出最為簡潔的代碼。如果涉及通解還會相應的代碼模闆。