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


  轉載請注明原文地址:http://www.cnblogs.com/GodA/p/5180560.html

  學習動態規划問題(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 子序列為1

  前4個數 5前面有2小於5 d(4)=d(1)+1=2 子序列為2 5

  前5個數 6前面有2 5小於6 d(5)=d(4)+1=3 子序列為2 5 6

  前6個數 4前面有2小於4 d(6)=d(1)+1=2 子序列為2 4

  前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 }

  其實當時還不知道最長上升子序列到底是啥東東。。現在學習動態規划就順便復習了一下。也就記錄下來,給自己看看吧。

  如有錯誤,敬請指出!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM