什么是最長遞增子序列呢? #include <iostream> using namespace std; int main() { int i,j,n,a[100],b[100],max; while(cin>>n) { for(i=0;i<n;i++) cin>>a[i]; b[0]=1;//初始化,以a[0]結(jié)尾的最長遞增子序列長度為1 for(i=1;i<n;i++) { b[i]=1;//b[i]最小值為1 for(j=0;j<i;j++) if(a[i]>a[j]&&b[j]+1>b[i]) b[i]=b[j]+1; } for(max=i=0;i<n;i++)//求出整個(gè)數(shù)列的最長遞增子序列的長度 if(b[i]>max) max=b[i]; cout<<max<<endl; } return 0; } 顯然,這種方法的時(shí)間復(fù)雜度仍為o(n^2); #include <iostream> using namespace std; int find(int *a,int len,int n)//若返回值為x,則a[x]>=n>a[x-1] { int left=0,right=len,mid=(left+right)/2; while(left<=right) { if(n>a[mid]) left=mid+1; else if(n<a[mid]) right=mid-1; else return mid; mid=(left+right)/2; } return left; } void fill(int *a,int n) { for(int i=0;i<=n;i++) a[i]=1000; } int main() { int max,i,j,n,a[100],b[100],c[100]; while(cin>>n) { fill(c,n+1); for(i=0;i<n;i++) cin>>a[i]; c[0]=-1;// …………………………………………1 c[1]=a[0];// ……………………………………2 b[0]=1;// …………………………………………3 for(i=1;i<n;i++)// ………………………………4 { j=find(c,n+1,a[i]);// ……………………5 c[j]=a[i];// ………………………………6 b[i]=j;//……………………………………7 } for(max=i=0;i<n;i++)//………………………………8 if(b[i]>max) max=b[i]; cout<<max<<endl; } return 0; } 對于這段程序,我們可以用算法導(dǎo)論上的loop invariants來幫助理解. 列有多個(gè),剛保存末尾元素最小的那個(gè).(這一性質(zhì)決定是第3條性質(zhì)成立的前提) 的遞增了序列只有一個(gè),c[1]也是最小的; c進(jìn)入循環(huán)前單調(diào)遞增及find函數(shù)的性質(zhì)可知(見find的注釋), 此時(shí)c[j+1]>c[j]>=a[i]>c[j-1],所以把c[j]的值更新為a[i]后,c[j+1]>c[j]>c[j-1]的性質(zhì)仍然成 立,即c仍然是單調(diào)遞增的; 小不會(huì)變大,因?yàn)檫M(jìn)入循環(huán)前c[j]的值是最小的,則循環(huán)中把c[j]更新為更小的a[i],當(dāng) 然此時(shí)c[j]的值仍是最小的; 為j有:c[j-1]<a[i]<=c[j],這說明c[j-1]是小于a[i]的,且以c[j-1]結(jié)尾的遞增子序列有最大的 長度,即為j-1,把a(bǔ)[i]接在c[j-1]后可得到以a[i]結(jié)尾的最長遞增子序列,長度為(j-1)+1=j; 增子序列的長度均已求出,再通過第8行的循環(huán),即求出了整個(gè)數(shù)組的最長遞增子序列。 仔細(xì)分析上面的代碼可以發(fā)現(xiàn),每次循環(huán)結(jié)束后,假設(shè)已經(jīng)求出c[1],c[2],c[3],...,c[len]的值,則此時(shí)最長遞增子序列的長度為len,因此可以把上面的代碼更加簡化,即可以不需要數(shù)組b來輔助存儲(chǔ),第8行的循環(huán)也可以省略。 #include <iostream> using namespace std; int find(int *a,int len,int n)//修改后的二分查找,若返回值為x,則a[x]>=n { int left=0,right=len,mid=(left+right)/2; while(left<=right) { if(n>a[mid]) left=mid+1; else if(n<a[mid]) right=mid-1; else return mid; mid=(left+right)/2; } return left; } int main() { int n,a[100],b[100],c[100],i,j,len;//新開一變量len,用來儲(chǔ)存每次循環(huán)結(jié)束后c中已經(jīng)求出值的元素的最大下標(biāo) while(cin>>n) { for(i=0;i<n;i++) cin>>a[i]; b[0]=1; c[0]=-1; c[1]=a[0]; len=1;//此時(shí)只有c[1]求出來,最長遞增子序列的長度為1. for(i=1;i<n;i++) { j=find(c,len,a[i]); c[j]=a[i]; if(j>len)//要更新len,另外補(bǔ)充一點(diǎn):由二分查找可知j只可能比len大1 len=j;//更新len } cout<<len<<endl; } return 0; } |
求最長遞增部分序列是一個(gè)比較常見的動(dòng)態(tài)規(guī)劃題。在導(dǎo)彈攔截等題中都有用到。一般來說就是用經(jīng)典的O(n^2)的動(dòng)態(tài)規(guī)劃算法。 算法如下: 設(shè)A[i]表示序列中的第i個(gè)數(shù),F(xiàn)[i]表示從1到i這一段中以i結(jié)尾的最長上升子序列的長度,初始時(shí)設(shè)F[i] = 0(i = 1, 2, ..., len(A))。則有動(dòng)態(tài)規(guī)劃方程:F[i] = max{1, F[j] + 1} (j = 1, 2, ..., i - 1, 且A[j] < A[i])。 然而在hoj10027中n的值達(dá)到了50000。顯而易見經(jīng)典算法是會(huì)超時(shí)滴。所以只有另謀出路了。 用一個(gè)變量len記錄到目前為止所找出來的最長遞增序列的長度。另外準(zhǔn)備一個(gè)數(shù)組b[],用這個(gè)數(shù)組表示長度為j的遞增序列中最后一個(gè)元素的值。在這里長度為j的遞增序列不止一個(gè),我們所要保存是那個(gè)最小的。為什么呢?因?yàn)樽詈笠粋€(gè)元素越小,那么這個(gè)遞增序列在往后被延長的機(jī)會(huì)越大。初始化b[0] = -1;len = 0;從第一個(gè)元素a[1]開始 a[i]( 1 <= i <= n)。如果這個(gè)元素比len長的序列的最大值大。則把這個(gè)元素直接添加到b數(shù)組的后面。如果這個(gè)元素比b數(shù)組的第一個(gè)元素還要小則把這個(gè)元素賦給b數(shù)組的第一個(gè)值。否則進(jìn)行二分查找。當(dāng)在b數(shù)組里面找到一個(gè)數(shù)比a[i]小,并且他的后面的數(shù)大于或等于a[i]則跳出。將a[i]添加到這個(gè)數(shù)的后面。輸出len就可以了。 代碼如下: #include <stdio.h> |