最长递增子序列,顾名思义是找到序列中递增序列的最长长度。
基本概念
设L=<a1,a2,…,an>是n个不同的实数的序列,L的递增子序列是这样一个子序列Lin=<aK1,ak2,…,akm>,其中k1<k2<…<km且aK1<ak2<…<akm。求最大的m值。
方法一:排序并使用LCS求解
基本思想:排序后原序列成为递增的序列,那么只需要找到和原来序列的最长公共子序列即可
#include<stdio.h>
#include<stdlib.h>
int a[105];
int b[105];
int dp[105][105]={1};
int cmp( const void *a, const void *b)
{
return *(int *)a - *(int *)b;
}
int max(int a,int b){return a>b?a:b;}
int main()
{
int n;
int i,j;
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%d",&a[i]);
b[i]=a[i];
}
qsort(b,n,sizeof(b[0]),cmp);
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{
if(a[i-1]==b[j-1])
dp[i][j]=dp[i-1][j-1]+1;
else
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
printf("%d\n",dp[n-1][n-1]);
return 0;
}
方法二:动态规划求解
基本思想:找到当前位置以前的最长序列,接着再拿当前位置与已存序列比较,看是否可以加入序列,那么到最后一个数时 dp【n】已经更新为最长的递增子序列
#include<stdio.h>
#include<string.h>
int max(int a,int b){
return a>b?a:b;
}
int main()
{
char dp[105];
memset(dp,1,strlen(dp));
int a[105];
int n;
int i;
scanf("%d",&n);
for( i=0;i<n;i++)
scanf("%d",&a[i]);
for( i=1;i<n;i++)
for(int j=0;j<i;j++)
if(a[j]<a[i]&&dp[j]>dp[i]-1)
dp[i] = dp[j]+1;
printf("%d",dp[n-1]);
return 0;
}
方法三:dp加二分
思想:在方法二中,每一次都要将到当前位置以前所有的序列顺序查找一遍,这样加入序列有序则可以使用二分查找来降低时间复杂度,所以想到,将每一个递增序列的最末尾元素存到dp数组中,那样可以根据每一次更新来构造一个递增的dp数组,之后进行二分查找来判断当前位置可以为哪一个递增序列的末尾元素,换言之,找到每一个末尾元素,那么递增子序列会增加,这样扫完所有的元素就可以找到最长的子序列
#include<stdio.h>
int main()
{
int a[105];
int dp[105];
int len = 1;
int n;
int mid;
int i,j;
scanf("%d",&n);
for(i=0;i<n;i++)
scanf("%d",&a[i]);
dp[0]=-1;//默认输入数字全为正数
dp[1]=a[0];//长度为一的子序列先默认成第一个数
for(i=1;i<n;i++)
{
int r = 0;//由于将最长子序列的末尾元素进行打表,所以末尾元素越小越好
//那么,不同长度的子序列的末尾元素则递增有序,这样每扫到一个元素,找到合适的序列长度
//看是否可以当作末尾元素,dp数组有序则利用二分搜索。
int l = len;
while(r<=l)
{
mid = (r + l)/2;
if(dp[mid]<=a[i])
r=mid+1;//找到合适的位置进行更改序列的大小。找到可改变的序列的大小
else
l=mid-1;
}
dp[r] = a[i];//找到可改变的位置,将长度为r的最后一个元素改为a[i];
if(r>len)
len=r;//更新当前最长的递增子序列
}
printf("%d\n",len);
return 0;
}
上面三种方法的时间复杂度分别为
方法一:O(n^2)+O(nlogn)=O(n^2)
方法二:O(n^2)
方法三:O(nlogn)