DP算法之最长上升子序列
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[p] 阅读更多= arr[i]; } } return len; }
- 算法练习--- DP 求解最长上升子序列(LIS)
- DP之最长上升子序列O(n*logn)算法
- 算法练习--- DP 求解最长上升子序列(LIS)
- hdu 1025 Constructing Roads In JGShining's Kingdom 深夜又一波DP,最长上升子序列(O(nlogn)算法)!尼玛坑爹的输出啊!!
- DP之最长上升子序列O(n*logn)算法
- LIS(最长上升子序列两种算法模板)DP模板,并且输出序列
- POJ 2533 Longest Ordered Subsequence(DP 最长上升子序列)
- 最长上升子序列两种复杂度的算法 LIS
- hdu1025 dp(最长上升子序列LIS)
- HDU 1025 最长上升子序列变形 (dp+二分)
- POJ1609 UVALive2815 UVA1196 ZOJ1787 Tiling Up Blocks【二维最长上升子序列+DP】
- 最长公共上升子序列的另一个O(mn)的算法
- hdu 1160 dp (二维最长上升子序列 记录路径
- 最长公共上升子序列的另一个O(mn)的算法
- codevs 2188 最长上升子序列(DP)
- 【HDU1950】Bridging signals (最长上升子序列DP(nlogn))
- hdu 1069 Monkey and Banana(dp 最长上升子序列)
- HDU 1025.Constructing Roads In JGShining's Kingdom【最长上升子序列n×logn算法】【1月6】
- dp之最长上升子序列
- (POJ1836)Alignment <DP,最长上升子序列变形>