動態規劃:最長上升子序列(LIS)

  轉載請註明原文地址: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 }

  其實當時還不知道最長上升子序列究竟是啥東東。。如今學習動態規劃就順便複習了一下。也就記錄下來,給本身看看吧。

  若有錯誤,敬請指出!

相關文章
相關標籤/搜索