参考:http://qiemengdao.iteye.com/blog/1660229
不一定增长,减小也一样的→_→
一、复杂度为 O(n^2) 的方法:
【主要用来初步理解,题目容易超时】
给一个初始序列: a[10] = { 2 , 5 , 6 , 1 , 3 , 7 , 5 , 9 , 8 , 1 0 } ,【下标从0开始】,求它的的最长增长子序列的长度,并输出该序列
先给出答案吧:max_len=6 , 序列:{ 2 , 5 , 6 , 7 , 9 , 10 }
需要三个数组:
a[10] —— 存放最初的数组
f[10] —— 存放以a[i]为结尾的序列的长度,初始化全为1
【因为如果a[i]前面没有比a[i]小的,那么a[i]就是子序列的开头和结尾,长度为1】
pos[10] —— 存放a[i]所在的序列中,a[i]的前一个的位置,因为最后需要输出子序列,要用到它的。。。如果不需要输出,则不需要该数组
【初始化为自己的下标就好了。。。】
然后定义 i , j ,遍历一遍a[i],对每个a[i] ,去前面找 a[i] 能接在哪个序列后面
i=0 ,前面没有,不动 【i完全可以从1开始】
i=1 ,a[1]=5,去前面找
j=0 ,a[1]>a[0] ,f[1]<f[0]+1 【这说明a[i]接到a[j]后面,能让序列变长,如果f[1]>f[0]+1,接后面吃亏, 如果相等,接后面白费力气】,说明a[1] 可以接在 a[0] 后面,组成序列 {2,5} ,
然后更新f[1]=2 【长度为2】 ,pos[1]=0【因为5上一个元素是2,下标是0】
i=2 ,a[2]=6,去前面找
然后更新f[2]=2 【长度为2】 ,pos[2]=0【因为6上一个元素是2,下标是0】
然后更新f[2]=3 【长度为3】 ,pos[2]=1【因为6上一个元素是5,下标是1】
j=0 ,a[3]<a[0] ,不能接在后面
j=1 ,a[3]<a[1] ,不能接在后面
j=2 ,a[3]<a[2] ,不能接在后面然后f[3],pos[3]都不用更新了。。。
j=0 , a[4]>a[0] ,f[4]<f[0]+1,说明a[4] 可以接在 a[0] 后面,组成序列 {2,3} ,
然后更新f[4]=2 【长度为2】 ,pos[4]=0【因为3上一个元素是2,下标是0】
j=1 ,a[4]<a[1] ,不能接在后面
j=2 ,a[4]<a[2] ,不能接在后面
……后面的类似→_→当然程序中需要定义max_len 不断和f[i]比较,更新,记录子串的最大长度
最终:
最后是输出最大长度max_len
还要输出该子串,曾经做过一道题,用了递归输出,结果超时。。。o(╯□╰)o
后来是用了一个数组,把下标全部存进去了。。。再输出。。。AC
上代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
using namespace std;
int a[1000]; //存放初始序列的数组
int f[1000]; //存放以a[i]结尾的序列的最大长度
int pos[1000]; //存放a[i]所在序列的前一项的位置
int temp[1000]; //最后输出子序列的时候,用来存放下标,是倒序的
int max_len; //存放子序列的最长长度,每次更新
int max_subscript; //最长序列结尾元素的下标
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
max_len=max_subscript=0; //初始化为0
for(int i=0;i<n;i++) //初始化
{
scanf("%d",a+i);
f[i]=1; //f[]数组初始化为1,因为若a[i]前面没有比它小的,那么以a[i]结尾的子串长度为1
pos[i]=i; //pos[]数组初始化为自己的下标
}
for(int i=0;i<n;i++) //a[i]数组遍历一遍,对每个a[i],去前面找能将a[i]最为结尾的序列
{
for(int j=0;j<i;j++) //对每个a[i],去前面找能将a[i]最为结尾的序列
{
if(a[i]>a[j]) //最长增长子序列,当然得后一个比前一个大咯~~~如果是递减,小于即可
if(f[i]<f[j]+1) //这个限制条件一开始有点难相同,挺难说明的,看着数组,多推演几遍就能想明白的→_→
{
f[i]=f[j]+1;
pos[i]=j; //记录前驱的下标,后面会用于输出
if(f[i]>max_len)
{
max_len=f[i]; //更新最长长度
max_subscript=i; //更新最长序列结尾的下标
}
}
}
}
printf("%d\n",max_len); //输出最长的子序列的长度
// for(int i=0;i<n;i++) //输出f[]数组看看
// printf("%d ",f[i]);
// printf("\n");
//
// for(int i=0;i<n;i++) //输出pos[]数组看看
// printf("%d ",pos[i]);
// printf("\n");
for(int i=0;i<max_len;i++) //记录下标
{
temp[i]=max_subscript;
max_subscript=pos[max_subscript]; //往前推
}
for(int i=max_len-1;i>=0;i--) //输出最长增长子序列
printf("%d ",a[temp[i]]);
printf("\n");
}
return 0;
}
二、复杂度为 O(nlogn) 的方法:
这个方法我暂时不会输出子序列。。。
给一个初始序列: a[10] = { 2 , 5 , 6 , 1 , 3 , 7 , 5 , 9 , 8 , 1 0 } ,【下标从0开始】,求它的的最长增长子序列的长度
先给出答案吧:max_len=6 , 序列:{ 2 , 5 , 6 , 7 , 9 , 10 }
需要两个数组:
a[10] —— 存放最初的数组
f[10] —— 存放中间结果,并不是子序列,不需要初始化成什么,每次之前的值会被覆盖的
int len —— 记录长度,初始化为0
先说为什么复杂度是O(nlogn)
首先O(n) 是遍历一遍 a[] 数组,如果 a[i] 比f[]数组中最大的数【就是最后那个】还要大,直接放入 f[] 数组最后,长度+1
但是如果 a[i] 不比最大的大,对每个a[i],去 f[] 数组中查找能插入的位置,就是找到第一个大于等于它的数的位置,覆盖掉,但是不会影响长度,所以len不变。
该过程采用二分的方法找位置,所以复杂度是O(logn)
最坏情况就是每个a[i]都要二分去找插入的位置,所以最坏情况复杂度就是O(nlogn)
len=0
第一步:f[0]=a[0] ,len=1
然后开始遍历,i从1开始
i=1:a[1]>f[0],放入f[]数组,f[1]=a[1]=5 ,len=2,f[]:{2,5}
i=2:a[2]>f[1],放入f[]数组,f[2]=a[2]=6 ,len=3,f[]:{2,5,6}
i=3:a[3]<f[2],在f[]中找第一个大于等于a[3]的数,即f[0],用a[3]覆盖,长度不变,len=3,f[]:{1,5,6}
i=4:a[4]<f[2],在f[]中找第一个大于等于a[4]的数,即f[1],用a[4]覆盖,长度不变,len=3,f[]:{1,3,6}
i=5:a[5]>f[2],放入f[]数组,len=4,f[]:{1,3,6,7}
i=6:a[6]<f[3],在f[]中找第一个大于等于a[6]的数,即f[2],用a[6]覆盖,长度不变,len=4,f[]:{1,3,5,7}
i=7:a[7]>f[3],放入f[]数组,f[4]=a[7]=9 ,len=5,f[]:{1,3,,5,7,9}
i=8:a[8]<f[4],在f[]中找第一个大于等于a[8]的数,即f[4],用a[8]覆盖,长度不变,len=5,f[]:{1,3,5,7,8}
i=9:a[9]>f[4],放入f[]数组,f[5]=a[9]=10 ,len=6,f[]:{1,3,5,7,8,10}
f[]数组仅仅用来存放中间结果,
f[]中的序列不是最长递增子序列。。。
但是长度正是最长递增子序列的长度,
上代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
using namespace std;
int a[1000]; //存放初始数列
int f[1000]; //存放中间结果,不是最长增长子序列
int BiSearch (int x,int left,int right)
{
if(left<=right)
{
int mid=(left+right)/2;
if(f[mid]==x)
return mid;
if(f[mid]>x)
return BiSearch(x,left,mid-1);
if(f[mid]<x)
return BiSearch(x,mid+1,right);
}
else
return left; //可以推演一下,如果f[]中不存在与a[i]相等的元素,那么第一个比它大的一定是最后left>right的left
}
int main()
{
int n,len;
while(scanf("%d",&n)!=EOF)
{
for(int i=0;i<n;i++)
scanf("%d",a+i); //初始化a[]数组
f[0]=a[0]; //把a[0]存入f[0],然后就开始遍历了。。。
len=1; //len表示最长增长子序列的长度,初始化为1,因为f[0]=a[0];了
for(int i=1;i<n;i++)
{
if(a[i]>f[len-1]) //如果a[i]>f[]中最大的,就直接加进f[]数组,长度加1
f[len++]=a[i];
else //否则,在f[]数组中找到第一个大于等于a[i]的数的下标,用a[i]覆盖,长度不变
{
int pos=BiSearch(a[i],0,len-1); //二分查找【递归】
f[pos]=a[i]; //覆盖
}
}
printf("%d\n",len); //最后输出最长递增子序列的长度
// for(int i=0;i<len;i++) //打印f[]数组,里面存的是中间结果,不是最长递增子序列
// printf("%d ",f[i]);
// printf("\n");
}
return 0;
}