您的位置:首页 > 其它

DP算法之最长上升子序列

2018-09-04 21:14 99 查看

1.问题描述

描述
一个数的序列bi,当b1 < b2 < ... < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1, a2, ..., aN),我们可以得到一些上升的子序列(ai1, ai2, ..., aiK),这里1 <= i1 < i2 < ... < iK <= N。比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。这些子序列中最长的长度是4,比如子序列(1, 3, 5, 8).

你的任务,就是对于给定的序列,求出最长上升子序列的长度。
输入
输入的第一行是序列的长度N (1 <= N <= 1000)。第二行给出序列中的N个整数,这些整数的取值范围都在0到10000。
输出
最长上升子序列的长度。
样例输入
7
1 7 3 5 9 4 8
样例输出
4

2.解题思路

第一种方法:常规解法(时间复杂度为O(n^2))

1)找子问题
一个上升子序列中最右边的那个数,称为该子序列的“终点”。
即可转化成:
“求以ak(k=1,2,3,.....,N)为终点的最长上升子序列的长度”

虽然这个子问题和原问题形式上并不完全一样,但是只要这N个子问题都解决了,那么这N个子问题的解中,最大的那个就是整个问题的解。

2)确定状态
子问题只和一个变量(数字的位置相关),因此序列中数的位置k就是“状态”,而状态k对应的“值”,就是以ak作为“终点”的最长上升子序列的长度。
状态一共有N个。

3)找出状态转移方程
maxLen(k)表示以ak作为“终点”的最长上升子序列的长度

那么:

maxLen(k)的值,就是在ak左边,“终点”数值小于ak,且长度最大的那个上升子序列的长度再加1。因为ak左边任何“终点”小于ak的子序列,加上ak后就能形成一个更长的子序列。

程序如下:

[code]#include <iostream>
#define MaxSize 1001
using namespace std;
int MaxInArray(int * arr,int n);
int main(){
int n;
int MaxLen[MaxSize];//以i为终点的最长上升子序列的长度
int array[MaxSize];
cin >> n;
for(int i =1;i<=n;++i){
cin >> array[i];
MaxLen[i] = 1;//赋初值
}
for(int i=1;i<=n;++i)
//每次求以第i个数为终点的最长子序列的长度
for(int j =1;j<=i;++j)
//察看以第j个数为终点的最长上升子序列
if(array[i] > array[j])
MaxLen[i] = max(MaxLen[i],MaxLen[j]+1);
cout << MaxInArray(MaxLen,n);
return 0;
}
int MaxInArray(int *arr,int n){
int Max = arr[1];
for(int i=2;i<=n;++i)
if(Max < arr[i])
Max = arr[i];
return Max;
}

第二种方法(时间复杂度为O(nlogn))

后来在遇到一道LIS的题,用上面O(nlogn)的过不了,然后了解到下面这种解法:
我们定义一个最小的有序序列lens[],其长度为len。
用题目给的1 7 3 5 9 4 8来举例。
我们定义一个数组a[7]={1,7,3,5,9,4,8}
a[1] = 1,我们把1放进lens[1],然后序列lens的长度len+1,len = 1
a[2] = 7,a[2]>lens[1],我们把7放进lens[2],然后序列lens的长度len+1,len = 2
a[3] = 3,a[3]<lens[2],因为序列lens存放的是最小的有序序列,所以我们把lens[2]替换成  
a[3],此时lens[2] = 3,因为此时只是替换,并没有使lens变长,所以len不变
a[4] = 5,a[4]>lens[2],我们把5放进lens[3],然后序列lens的长度len+1,len = 3
a[5] = 9,a[5]>lens[3],我们把9放进lens[4],然后序列lens的长度len+1,len = 4
a[6] = 4,a[6]<lens[3],我们把lens[3]替换成a[6],此时lens[3] = 4,因为此时只是替换,并没有使lens变长,所以len不变
a[7] = 8,a[7]<lens[4],我们把lens[4]替换成a[7],此时lens[4] = 8,因为此时只是替换,并没有使lens变长,所以len不变
最终len为4,和我们所要求的长度结果是一致的,但是此时的有序序列不一定就是我们的最长上升子序列,此方法只能求长度,但是不能用来求真正的最长上升子序列

经过上面的例子,我们可以得出

如果a[i]>lens[len],lens[++len] = a[i],否则则从lens这有序序列中找出一个下界x(找到一个最大的x满足len[x]<a[i]),然后将lens[x]替换为a[i],此时查找下界用二分查找,即可使整个算法的时间复杂度为O(nlogn)

改进版程序代码:

[code]#include <iostream>
using namespace std;
int arr[100000];
int search(int l,int r,int num,int a[]);
int LIS(int n);
int main(){
int n;
cin >> n;
for(int i=1;i<=n;++i)
cin >> arr[i];
cout << LIS(n) <<endl;
return 0;
}
int search(int l,int r,int num,int a[]){
int mid;
while(l<=r){
mid = (l+r)>>1;
if(a[mid]<=num)
l = mid+1;
else
r = mid - 1;
}
return l;
}
int LIS(int n){
int * lens = new int [n+1];
int len = 1;
lens[1] = arr[1];
for(int i = 2;i<=n;++i){
if(arr[i]>lens[len])
lens[++len] = arr[i];
else{
int p = search(1,len,arr[i],lens);
lens

= arr[i]; } } return len; }

[p] 

阅读更多
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: