轉載請註明原文地址:http://www.cnblogs.com/GodA/p/5180560.htmlhtml
學習動態規劃問題(DP問題)中,其中有一個知識點叫最長上升子序列(longest increasing subsequence),也能夠叫最長非降序子序列,簡稱LIS。簡單說一下本身的心得。前端
咱們都知道,動態規劃的一個特色就是當前解能夠由上一個階段的解推出, 由此,把咱們要求的問題簡化成一個更小的子問題。子問題具備相同的求解方式,只不過是規模小了而已。最長上升子序列就符合這一特性。咱們要求n個數的最長上升子序列,能夠求前n-1個數的最長上升子序列,再跟第n個數進行判斷。求前n-1個數的最長上升子序列,能夠經過求前n-2個數的最長上升子序列……直到求前1個數的最長上升子序列,此時LIS固然爲1。算法
讓咱們舉個例子:求 2 7 1 5 6 4 3 8 9 的最長上升子序列。咱們定義d(i) (i∈[1,n])來表示前i個數以A[i]結尾的最長上升子序列長度。編程
前1個數 d(1)=1 子序列爲2;學習
前2個數 7前面有2小於7 d(2)=d(1)+1=2 子序列爲2 7測試
前3個數 在1前面沒有比1更小的,1自身組成長度爲1的子序列 d(3)=1 子序列爲1spa
前4個數 5前面有2小於5 d(4)=d(1)+1=2 子序列爲2 5code
前5個數 6前面有2 5小於6 d(5)=d(4)+1=3 子序列爲2 5 6htm
前6個數 4前面有2小於4 d(6)=d(1)+1=2 子序列爲2 4blog
前7個數 3前面有2小於3 d(3)=d(1)+1=2 子序列爲2 3
前8個數 8前面有2 5 6小於8 d(8)=d(5)+1=4 子序列爲2 5 6 8
前9個數 9前面有2 5 6 8小於9 d(9)=d(8)+1=5 子序列爲2 5 6 8 9
d(i)=max{d(1),d(2),……,d(i)} 咱們能夠看出這9個數的LIS爲d(9)=5
總結一下,d(i)就是找以A[i]結尾的,在A[i]以前的最長上升子序列+1,當A[i]以前沒有比A[i]更小的數時,d(i)=1。全部的d(i)裏面最大的那個就是最長上升子序列。話很少說,show me the code!下面是代碼實現的算法。
1 int LIS(int A[],int n) 2 { 3 int* d = new int[n]; 4 int len = 1; 5 int i,j; 6 for(i=0;i<n;i++) 7 { 8 d[i]=1; 9 for(j=0;j<i;j++) 10 { 11 if(A[j]<=A[i] && (d[j]+1)>=d[i]) 12 d[i]=d[j]+1; 13 } 14 if(d[i]>len) len=d[i]; 15 } 16 delete []d; 17 return len; 18 }
這個算法的時間複雜度爲〇(n²),並非最優的算法。在限制條件苛刻的狀況下,這種方法行不通。那麼怎麼辦呢!有沒有時間複雜度更小的算法呢?說到這裏了,固然是有的啦!還有一種時間複雜度爲〇(nlogn)的算法,下面就來看看。
咱們再舉一個例子:有如下序列A[]=3 1 2 6 4 5 10 7,求LIS長度。
咱們定義一個B[i]來儲存可能的排序序列,len爲LIS長度。咱們依次把A[i]有序地放進B[i]裏。(爲了方便,i的範圍就從1~n表示第i個數)
A[1]=3,把3放進B[1],此時B[1]=3,此時len=1,最小末尾是3
A[2]=1,由於1比3小,因此能夠把B[1]中的3替換爲1,此時B[1]=1,此時len=1,最小末尾是1
A[3]=2,2大於1,就把2放進B[2]=2,此時B[]={1,2},len=2
同理,A[4]=6,把6放進B[3]=6,B[]={1,2,6},len=3
A[5]=4,4在2和6之間,比6小,能夠把B[3]替換爲4,B[]={1,2,4},len=3
A[6]=5,B[4]=5,B[]={1,2,4,5},len=4
A[7]=10,B[5]=10,B[]={1,2,4,5,10},len=5
A[8]=7,7在5和10之間,比10小,能夠把B[5]替換爲7,B[]={1,2,4,5,7},len=5
最終咱們得出LIS長度爲5。可是,可是!!這裏的1 2 4 5 7很明顯並非正確的最長上升子序列。是的,B序列並不表示最長上升子序列,它只表示相應最長子序列長度的排好序的最小序列。這有什麼用呢?咱們最後一步7替換10並無增長最長子序列的長度,而這一步的意義,在於記錄最小序列,表明了一種「最可能性」。假如後面還有兩個數據8和9,那麼B[6]將更新爲8,B[7]將更新爲9,len就變爲7。讀者能夠自行體會它的做用。
由於在B中插入的數據是有序的,不須要移動,只須要替換,因此能夠用二分查找插入的位置,那麼插入n個數的時間複雜度爲〇(logn),這樣咱們會把這個求LIS長度的算法複雜度降爲了〇(nlogn)。話很少說了,show me the code!
1 int put(int arr[], int l, int r, int key)//在arr[l...r]中二分查找插入位置 2 { 3 int mid; 4 if (arr[r] <= key) 5 return r + 1; 6 while (l < r) 7 { 8 mid = l + (r - l) / 2; 9 if (arr[mid] <= key) 10 l = mid + 1; 11 else 12 r = mid; 13 } 14 return l; 15 } 16 17 int LIS(int A[], int n) 18 { 19 int i = 0, len = 1 ,next; 20 int* B = (int *)alloca(sizeof(int) * (n + 1)); 21 B[1] = A[0]; 22 for (i = 1;i < n;i++) 23 { 24 int next = put(B, 1, len, A[i]); 25 B[next] = A[i]; 26 if (len < next) len = next; 27 } 28 return len; 29 }
說了那麼多,這個到底有什麼用途呢?由於咱們新生賽中就有這一題,那就一塊兒來看一下實例吧!
Example:
好多好多球
Time Limit:1000MS Memory Limit:65535K
題型: 編程題 語言: 無限制
描述
一天,Jason買了許多的小球。有n個那麼多。他寫完了做業以後就對着這些球發呆,這時候鄰居家的小朋友ion回來了,
Jason無聊之際想到了一個遊戲。他把這n個小球從1到n進行標號。而後打亂順序,排成一排。而後讓ion進行一種操做:
每次能夠任意選擇一個球,將其放到隊列的最前端或者隊列的最末尾。問至少要進行多少次操做才能使得球的順序變成正序1,2,3,4,5……n。
輸入格式
包含多組測試數據,每組數據第一行輸入一個n(1 <= n <= 100),表示有n個球。第二行有n個數字ai(1 <= ai <= n),ai兩兩各不相同。
輸出格式
每組測試數據輸出佔一行,表示最少的操做次數使得小球變得有序。
輸入樣例
4
3 2 1 4
2
2 1
輸出樣例
2
1
分析:題意是把n個亂序的數變爲順序,移動次數最少。一樣是用到了最長上升子序列,這裏的上升,是連續的、等差的,由於n個球的編號就是從1~n,因此咱們找到每次遞增1的最長子序列,剩下的數只要移到隊頭或者隊尾就能夠了。那麼移動最少次數就等於n-LIS。話很少說,show me the code! 當時是用C來寫的,其實都是同樣的。
1 #include <stdio.h> 2 int main() 3 { 4 int a[150]; 5 int n,i,j,x,count,maxlist; 6 while(scanf("%d",&n)!=EOF) 7 { 8 for(i=0;i<n;i++) 9 scanf("%d",&a[i]); 10 maxlist=1; 11 if(n==1) printf("0\n"); 12 else 13 { 14 for(i=0;i<n;i++) 15 { 16 x=a[i]; 17 count=1; 18 for(j=i+1;j<n;j++) 19 { 20 if(a[j]==x+1) 21 { 22 x++; 23 count++; 24 } 25 } 26 if(count>maxlist) 27 maxlist=count; 28 } 29 printf("%d\n",n-maxlist); 30 } 31 } 32 }
其實當時還不知道最長上升子序列究竟是啥東東。。如今學習動態規劃就順便複習了一下。也就記錄下來,給本身看看吧。
若有錯誤,敬請指出!