最長上升子序列 LIS【DP的O(n^2)及貪心+二分的O(nlogn)解法】
技術標籤:資料結構和演算法
最長上升子序列
給你一個長度為N的序列,求其最長上升子序列的長度。
樣例輸入:
6
1 6 2 4 3 5
樣例輸出:
4
解釋:其最長上升子序列的長度為4。可以是{1 2 4 5 }或者{1 2 3 5}
注意:子序列是不連續的。
假設我們把序列儲存在a陣列中,並且從下標1開始儲存。
動態規劃解法
定義 f[i] 表示到a[i] 為止的最長上升子序列的長度。(其中a[i]必須被選中,必須以a[i]結尾)
所以動態轉移方程是 f [ i ] = max(f [ j ] ) + 1.( 1 <= j < i 並且 a [ j ] > a [ i ] )
也就是說 對於位置i來說,只要前面的a[ j ]小於a[ i ],那麼就可以轉移過來,從中選擇一個最大的即可。
程式碼:
#include<iostream> using namespace std; const int maxn=10000; int a[maxn],f[maxn]; int main() { int n; cin>>n; for (int i=1; i<=n; i++) cin>>a[i]; for (int i=1; i<=n; i++){ //n個數都得算f[i] f[i]=1; //最起碼是個1 for (int j=1; j<i; j++){ //前面的所有j if (a[j]<a[i]) //滿足小於 f[i]=max(f[i],f[j]+1); //選最大的 } } int ans=0; for (int i=1; i<=n; i++) //統計答案 if (f[i]>ans) ans=f[i]; cout<<ans<<endl; }
時間複雜度:雙重迴圈,所以複雜度是O(n^2).
貪心+二分解法
對上述的動態規劃演算法考慮優化,外層迴圈,也就是每個數都得計算f[i],這個肯定沒有辦法再優化了。
所以優化的重點就在於內層迴圈找 i 前面的比 a[ i] 小的最大的 f [ j ]。
說到查詢,大家第一時間都會想到二分,但是二分要求陣列得是有序的,f 陣列不滿足,那麼我們可以使得 f 陣列有序嗎?
我們先把樣例的 f 陣列寫出來,如下:
我們觀察 f 陣列劃線的兩個1,前一個表示的是以a[1]也就是1結尾的最長上升子序列長度是1,後一個表示以a[2]也就是6為結尾的最長上升子序列長度也是1。
這兩個1對於後面的轉移是有影響的,但是我們發現,只要是6能轉移的,1肯定都能轉移,所以這個6我們沒有必要儲存下來。
到這裡我們可以總結出,在相同的長度下,我們想盡量讓a[i]更小,這樣狀態更能轉移到後面去,這種優化就是貪心思想的體現。比如剛剛說的樣例,同樣是長度為1,以1結尾比以6結尾好多了,所以我們選擇1,而不用6,劃線的4和3也是同樣的情況。
所以,對於每個長度,我們只需要記錄其最小的那個a[i]。 假設我們把這個陣列稱為g。
g[i]表示長度為 i 的最長上升子序列的最小結尾是 g[ i ]。g陣列的長度就是截止當前的最長上升子序列的長度。
比如當前 g 陣列有m個。
如果 a[i] >g[m] 說明可以用 a[i] 繼續擴充套件.
如果 a[i]<g[m] 對於當前答案沒有影響,但是 a[i] 可能會優化g陣列,所以我們要在 g 陣列中找到第一個小於 a[i] 的數,去優化g陣列。因為g陣列單調的,可以使用二分查詢。
程式碼:
#include<iostream>
using namespace std;
const int maxn=10000;
int a[maxn],g[maxn];
int main() {
int n;
cin>>n;
for (int i=1; i<=n; i++)
cin>>a[i];
int m=0;
g[0]=-100000000; //初始化
for (int i=1; i<=n; i++){
if (a[i]>g[m]){ //滿足就擴充套件
m++;
g[m]=a[i];
}else{ //否則二分
int l=0,r=m;
while (l!=r){
int mid=(l+r+1)/2;
if (g[mid]<=a[i]) l=mid;
else r=mid-1;
}
if (g[l]!=a[i]) g[l+1]=a[i]; //特判下相等的情況
}
}
cout<<m<<endl; //最長上升子序列的長度就是g陣列的長度
}
其中,初始化g[0]為負值,目的是讓a[1]一定可以選入。當然你可以選擇先在g陣列中加入a[1].
二分完畢後,有一個判斷語句,if (g[l]!=a[i]) g[l+1]=a[i]; 因為我們求得是嚴格上升的,所以相同的情況下是不能更新g的。
答案就是g陣列的長度。
時間複雜度:O(nlogn)。