最長上升子序列O(nlogn)算法詳解


最長上升子序列

時間限制: 10 Sec   內存限制:128 MB

題目描述

給定一個序列,初始為空。現在我們將1到N的數字插入到序列中,每次將一個數字插入到一個特定的位置。我們想知道此時最長上升子序列長度是多少?

輸入

第一行一個整數N,表示我們要將1到N插入序列中,接下是N個數字,第k個數字Xk,表示我們將k插入到位置Xk(0<=Xk<=k-1,1<=k<=N)

輸出

1行,表示最長上升子序列的長度是多少。

樣例輸入

3

0 0 2

樣例輸出

2

提示

100%的數據 n<=100000

O(nlogn)算法代碼

 1 #include <iostream>
 2 using namespace std; 
 3 int i,j,n,s,t,a[100001];
 4 int main()
 5 { 
 6     cin>>n;
 7     a[0]=-1000000;
 8     for(i=0;i<n;i++)
 9     {
10         cin>>t;/* 比棧頂元素大數就入棧 */
11         if(t>a[s]) a[++s]=t;
12         else
13         {
14             int l=1,h=s,m;
15 /* 二分檢索棧中比t大的第一個數 */
16             while(l<=h)
17             {
18                 m=(l+h)/2;
19                 if(t>a[m]) l=m+1;
20                 else h=m-1;
21             }/* 用t替換 */
22             a[l]=t;
23         }
24     }/* 最長序列數就是棧的大小 */
25     cout<<s<<endl;
26 }
View Code

代碼分析:

第一個念頭就是用動態規划,很顯然,這道題的轉移方程非常非常簡單,一目了然,先准備一個數組b

b[i]=1;
,從a[1]開始搜到i的最長上升子序列。

這句賦值語句固然很好理解,每一個元素,也可以視為一個符合題意的子序列。

b[2]呢?

如圖,它顯然比a[1]高,在執行如下語句時

for(j=1;j<i;j++)   if(a[i]>a[j])

j小於i,也就是2,目前符合條件的只有a[1]a[1]又通過了判斷語句,它確實小於a[i],執行下一條語句:

b[i]=max(b[i],b[j]+1);

很顯然:b[2]顯然原來是1,當它和b[1]+1比時,1當然比2小,所以,b[2]自然就是2了。

再來看看時間復雜度:

很明顯,時間復雜度為O(n^2)


那,這個方法夠快嗎?還可以,但仍然有些不盡人意。

代碼如下O(n^2):

 1 #include<iostream>  
 2 using namespace std;
 3 int i,j,n,a[100],b[100],max;    
 4 int main()  
 5 {
 6     cin>>n;
 7     for(i=0;i<n;i++) cin>>a[i];  
 8     b[0]=1; //初始化,以a[0]結尾的最長遞增子序列長度為1  
 9     for(i=1;i<n;i++)  
10     {  
11         b[i]=1;//b[i]最小值為1
12         for(j=0;j<i;j++)  
13             if(a[i]>a[j]) b[i]=max(b[i],b[j]+1);
14     }  
15     for(max=i=0;i<n;i++) if(b[i]>max) max=b[i];  
16     cout<<max<<endl;
17 }
View Code

那么,還有沒有更快的方法呢?

當然有,有沒有想到過,為什么要記錄數據呢?

我們可以模擬一個stack

在有大量數據的情況下,這算法效率極高

但是,怎么來優化程序呢?

我們可以這樣來模擬:

每輸入一個數,如果這個數大於棧頂的那個數,於是把它推入棧中。

 

但是,如果這個數大於棧頂呢,這不證明它不可以更新棧中的

某個元素,這時,就可以運用二分查找了。

     有人可能會問:這個序列是無序的啊。沒錯,但查找的是stack里面的元素,而這個棧里的所有元素,都是嚴格遞增的,所以,用二分查找可以把問題縮減為O(nlogn)

     有些不符合邏輯,不是嗎?15的下標比171820都大,為什么能插入呢?但是如果仔細想一想,這好像並不影響正常答案,但如果要輸出最長上升子序列,那就要改一改這個算法了。

整個二分查找代碼如下:

else

{

    int l=1,h=s,m;

    while(l<=h)

    {

         m=(l+h)/2;

         if(t>a[m]) l=m+1;

         else h=m-1;

    }

    a[l]=t;

}

由此,這個查找算法才得以下降到logn,於是,整體也就是O(nlogn)

具體操作如下:

每次取棧頂元素和讀到的元素做比較,如果大於,則將它入棧;如果小於,則二分查找棧中的比它大的第1個數,並替換它。最長序列長度即為最后模擬的大小。

這也是很好理解的,對於ij,如果i <ja[i] < a[j],a[i]替換a[j],長度雖然沒有改變但a'潛力'增大了。

代碼(同上):

 1 #include <iostream>
 2 using namespace std; 
 3 int i,j,n,s,t,a[100001];
 4 int main()
 5 { 
 6     cin>>n;
 7     a[0]=-1000000;
 8     for(i=0;i<n;i++)
 9     {
10         cin>>t;
11         if(t>a[s]) a[++s]=t;
12         else
13         {
14             int l=1,h=s,m;
15             while(l<=h)
16             {
17                 m=(l+h)/2;
18                 if(t>a[m]) l=m+1;
19                 else h=m-1;
20             }
21             a[l]=t;
22         }
23     }
24     cout<<s<<endl;
25 }
View Code


免責聲明!

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



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