小菜鸡刚学完,趁热记录一波;
最长上升子序列,顾名思义,就是求一段序列中的子序列,这些个子序列要求是不断上升的,并且是最长的,例如:
seq[]={1,2,3,1,2,3} //举这个例子是有特殊用意的,题目经常要求的是“最长不下降子序列” 下面我们举的例子都是最长不下降,事实上和最长上升也只是差一个等号而已;
seq的最长上不下降子序列就是,{1,1,2,3}或者{1,2,2,3},就是这样,不断上升(不下降即可),最长;
接下来介绍两种方法求最长上升子序列的长度,是的,下面这两种套路只能求长度,要输出的话需要技巧,等我厉害了在写;
方法一:dp, 复杂度O(N2);
首先一个数组记录输入数字,还是上面的 seq[7]={+oo,1,2,3,1,2,3}; 再来一个数组记录状态:dp[i] (什么意思?具体这个数组干啥用的? 它的每个元素代表着,从0到当前下标,并必须以当前下标为结尾,的最长的上升子序列的长度是多长);
接下来手写算法过程:
首先看seq的第一个元素 1: 它是序列的第一个元素,当前我们只看到这里,前面没有元素了,那最长子序列只有他自己一个,即{1},长度为1; 则记下长度dp[1]=1,
接下来看第二个元素 2:我们向前面的状态看,前面只有一个1,2比1大,所以通过1这个比他小的元素,他可以和1组成一个上升子序列{1,2},长度为2,前面记录过从 1之前到1 它最长的上升子序列长度是1,我们用了dp[1]=1记录下了它的状态,此时正是使用的时候,那么状态更新为 dp[2]=dp[1]+1=2;{1,2};
第三个元素 3:3比第一个元素 1 大,也比第二个元素2大,也就是说,3可以直接接到{1} 的状态后面,也可以直接接到{1,2} 后面, 我们要最长,自然是选第二个,记录第二个的状态是dp[2],那么 状态更新为 dp[3]=dp[2]+1=3;{1,2,3};
第四个元素 1: 上面说了求最长不下降,自然只要不比1小就行, 我们向前看,前面不比1小的元素只有第一个元素1,它的状态是dp[1]=1,{1},也就是说 我们只能从这个状态更新,因为只有第一个元素不比它大,那么我们可以更新为 dp[4]=dp[1]+1; {1,1};
第五个元素 2:前面比不比它大的元素只有 第一个元素1,第2个元素2,第四个元素1;状态分别是:dp[1]=1;dp[2]=2;dp[4]=2,也就是说 我们只能从这三个状态更新过来, 那我们要的是最长,自然,选dp[]数组值最大的,即{}里面的数最多的, 此时dp[2]=dp[4]=2,两个同时最大,因为只求长度,所以任选一个结果一样 dp[5]=dp[2]+1=3 {1,2,2} || dp[5]=dp[4]+1=3 {1,1,2};
第六个元素 3: 前面所有元素都不比它大,我们挑一个前面的最大的就行,此时dp[5]最大,是3,所以从dp[5]更新过来,dp[6]=dp[5]+1=4;
完毕,dp[]数组此时里面存的,就是以当前下标为结尾的最长不下降子序列的长度,那么我们只需要在里面找出最大的那一个,就是最长不下降子序列。 下面上代码
#include<stdio.h>
int main()
{
int seq[11];
int d[11]={0};
int i,n,j,max=0,mark=0;
scanf("%d",&n); //长度为n的序列
for(i=1;i<=n;i++)
{
scanf("%d",&seq[i]); //输入长度为n的序列
}
for(i=1;i<=n;i++) //遍历1~n,遍历到哪个元素,以哪个元素为结尾
{
for(j=1;j<=i;j++) //用来遍历此元素之前的元素
{
if(seq[i]>=seq[j]) //如果在这个元素之前找到了不大于它的数,如果是要找小于它的数,去掉等号即可
{
if(d[j]>max) max=d[j]; //max为临时变量,用于存储在此元素之前,最大的一个不大于它的元素的状态
mark=1; //标记一哈,意为前面有不大于它的数
}
}
if(mark==1) d[i]=max+1; //如果前面有不大于它的数,直接从那个数字更新
else d[i]=1; //如果没有,长度为1,因为只有它自己一个元素
mark=0;max=0; //重新赋值一哈
}
printf("dp[] = ");
for(i=1;i<=n;i++)
{
printf("%d ",d[i]); //输出状态数组的
if(d[i]>max) max=d[i]; //找最大的
}
printf("\n");
printf("最长不下降子序列长度是:%d\n",max); //输出最长不下降子序列的长度
return 0;
}
方法二:dp+二分, O(NlogN)
//累了,先到这吧- =~