最長上升子序列(LIS)的貪心演算法
技術標籤:演算法
我們知道通過dp的演算法可以求出一段序列的最長上升子序列,時間複雜度為o(n^2)
。下面來介紹一種o(nlogn)
的演算法來求LIS。
設一段序列的長度為n
,我們需要的是一個輔助陣列f
,長度最長為n
,其實際長度是動態的,也表徵了最長上升子序列的長度。
對於f[i]
,其儲存的是對於已經掃描過的序列中
,所有長度為i
的上升子序列
的最後一個數的最小值。如:
有序列1 4 2 3
所有長度為2的上升子序列有1 2,1 3.
那麼f[2] = 2
。
這樣做,可以保證對於同樣長度的上升子序列,我們給出的是其尾元素的最小值,那麼這樣在掃描後續序列時,就必定更有可能構造出最長上升子序列。
首先可以證明整個f
陣列是單調遞增的,即對於任意的索引i < j
,都有 f[i] < f[j]
。反證法如下:
若f[i] == f[j]
,對於f[j]
對應的長度為j
的最長上升子序列,其倒數第2個元素值一定小於f[j]
,那麼這個倒數第2個元素值可以作為f[i]
,則有f[i] < f[j]
。
同理可證f[i] > f[j]
的情形。
繼續分析:
如已掃描序列1 4 2 3,根據我們的定義,有f[3] = 3
(序列1 2 3的尾元素),再往後掃描時,如果是4,即序列為1 4 2 3 4時,就可以根據f[3] = 3
(3 < 4)往後延長一個LIS長度(即f
長度變為4),而且使得f[4] = 4
或者從反面考慮:如果我們f[i]
儲存的不是長度為i
的最長上升子序列的尾元素,如f[3] = 5
(存1 4 5的尾元素),那麼掃描後續元素就無法最大化地延長上升序列,無法達到題目要求。
注意在思考問題時,從集合的角度考慮問題,即每個f[i]
首先是關於所有長度為i
的上升序列的函式,其次,f[i]
存的是這些上升序列尾元素的最小值。
那麼在掃描原陣列的時候,對於第i
個數,可以根據前i-1
個數得到的f
陣列的結果,對f
陣列進行長度或內容的更新,具體表現如下:(設f
陣列的實際長度為cnt
)
1、若a[i] > f[cnt]
1至cnt
的f
中的每一個f[j]
,都有a[i] > f[j]
(因為前面已經證明f
單調遞增)。那麼當前的a[i]
可以作為長度為cnt+1
的上升子序列的最小元素(因為當前只有這麼一個長度為cnt+1
的上升序列),即f[cnt+1] = a[i]
,同時cnt++
.
2、若a[i] <= f[cnt]
,則無法使得上升序列變長,因為f[cnt]
是長度為cnt
的所有上升序列尾元素的最小值。因此,它的作用是,更新對應的一個f
。顯然,對於從後往前第一個小於a[i]
的f[j]
而言,a[i]
可以接到f[j]
所代表的那個最優上升序列後面,從而更新f[j+1]
(長度為j+1
的上升序列的尾元素最小值)。因為從剛才的敘述可以知道,原f[j+1] >= a[i]
。故這樣a[i]
的作用是直接改變f
的一個恰當元素值,同時為最終cnt
的更新作準備(通過將一個f
的元素值變小,使得後面元素接到前面更具有可能)。
AC程式碼:
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<stdlib.h>
#include<algorithm>
using namespace std;
const int N = 1e5+5;
int n;
int a[N],f[N];
int cnt = 0;
int Find(int x){
int l = 1,r = cnt;
while(l < r){
int mid = l+r >> 1;
if(f[mid] >= x)
r = mid;
else
l = mid+1;
}
return l;
}
int main(){
scanf("%d",&n);
for(int i = 1;i <= n;i++){
scanf("%d",&a[i]);
}
f[++cnt] = a[1];
for(int i = 2;i <= n;i++){
if(a[i] > f[cnt])
f[++cnt] = a[i];
else
f[Find(a[i])] = a[i];
}
printf("%d\n",cnt);
return 0;
}