1.問題描述:
求一個正整數序列的最長單調自增子序列,子序列不要求是連續的。例如
Input:5
5 2 4 3 1
Output:2
2. 算法復雜度是O(N*N)
f[i]是以a[i]為最大值的子序列,那么f[]的最大值就是要的結果。
int f[],a[];
f[0] = 1;
for(i = 1 ; i < n ; i++ )
{
f[i] = 1;
for(j = 0 ; j < i ; j++)
{
If(a[j] < a[i] && f[j]+1 > f[i])//等號有沒有要視題目而定
{
f[i] = f[j] +1;
}
}
}
很顯然實踐復雜度是O(N*N),那么有沒有更快的算法呢?按照正常的思路更快的復雜度應該就是O(N*logN),那么就要涉及到二分了。
3. 算法復雜度是O(N*logN)
可是話又說回來,那個logN到底怎么實現的呢?上網搜了搜說的都有點抽象,捉摸了一下,是這個樣子滴!建立一個輔助數組c[n],c[i]存儲的是子序列長度為i的序列最后一個值(實際上子序列長度為i的子序列有多個,要的是子序列最后一個值最小的。后面解釋后什么!!!)。這時要遍歷要處理的數組a[n],for(i=1;i<n;i++)//從第二值開始,因為第一個值用來初始化了
{
j=find(c,n+1,a[i]);//find是一個二分查找
c[j]=a[i];
b[i]=j;
}
請看一下上面的例子實際執行的情況:C數組變化的情況
-1 5
-1 2
-1 2 4
-1 1 2
-1 1 4
-1 1 3
A數組遍歷是從前往后的,處理a[i-1]時a[i]以及后面的值肯定還沒有處理,前面的值都處理過了,看c數組,每個a數組中的值和c數組中值進行比較,找到合適的位置插入(若插入到c數組的末尾,那么就屬於最長遞增子序列長度加1,實際上c數組的長度就是最后的最長單調遞增子序列的長度。),否則這就替換掉了c數組中原來位置存儲的值,這種替換時有意義的,主要是為了后來的a數組中的值計算b用(b[i]中保存的是以a[i]為最后一個元素的最長單調遞增子序列。)好處是若a[i] <a[j],b[i]=b[j],那么在c中肯定要保存a[i]呀!!(注意c數組的下標代表的是子序列的長度,c數組中的值也是按遞增順序排列的。這才可能用二分呢,親)。和O(N*N)的主要區別就是巧妙的借用了c數組,本題的關鍵就是理解c數組的意義。可以手動模擬一下算法執行的步驟,重要模擬c和b數組的變化情況。下面給出完整的算法。
#include <iostream>
using namespace std;
int find(int *a,int len,int n)//二分find
{
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;//要懂得用這種天然的最小值
c[1]=a[0];//初始化
b[0]=1;//b[i]表示以a[i]結尾的最長單調遞增子序列
for(i=1;i<n;i++)//復雜度是N的
{
j=find(c,n+1,a[i]);//復雜度是logN的
c[j]=a[i];
b[i]=j;
}
for(max=i=0;i<n;i++)//遍歷一遍找到最大值
if(b[i]>max)
max=b[i];
cout<<max<<endl;
}
return 0;
}