最长单调子序列lis
若a序列<a[1],a[2],…,a[n]>删去其中若干个元素后与b序列完全相同,则称b是a的子序列。
如果存在一个子序列L=<a[k1],a[k2],…,a[km]>,其中k1 < k2 < … < km且aK1 < ak2 < … < akm,那么序列L是一个单调递增子序列,如果其中的m最大,那么该序列L就是序列a的最长递增子序列。最长递增子序列,英文全称为Longest Increasing Subsequence,缩写为lis。
设dp[i]表示a序列中以ai为末元素的最长递增子序列的长度。则有如下的动态规划状态转移方程:
dp[i] = max{dp[j]} + 1 (j<i并且a[i]>a[j])
初始dp[i]=1,因为这个子序列中至少有a[i]1个元素。
【例题】最长递增子序列
所谓子序列,就是在原序列里删掉若干个元素后剩下的序列,以字符串"abcdefg"为例子,去掉bde得到子序列"acfg" 现在的问题是,给你一个数字序列,你要求出它最长的单调递增子序列。
【输入格式】
第一行是n(1<=n<=10000),下一行是n个比30000小的非负整数
【输出格式】
最长的单调递增子序列的长度
【样例输入】
5
1 10 4 9 7
【样例输出】
3
最长单调子序列属于线性动态规划。在上面的题目中,如果需要我们输出最长上升子序列中的每个元素,我们怎么办呢?
我们可以定义一个fa[i]数组,存储当前子序列中a[i]的前一个元素。最后递归反向输出。
例如输入:
13
7 9 16 38 24 37 18 44 19 21 22 63 15
输出:
8
7 9 16 18 19 21 22 63
最长下降子序列可转化为反向地求最长上升子序列。
二分法优化最长单调子序列
用二分法优化最长单调子序列,可快速获得子序列的长度(不是状态)。
二分法优化的最长上升子序列:
dp表示长度为i的上升子序列最后一个数最小是多少。显然数组dp是单增的。
读到一个新的数x后,找到某个i使得x>dp[i]且x<=dp[i+1],于是用x去更新dp[i+1];特别地,如果所有的dp[i]都小于x,则增加dp的长度。
最后看dp数组有多长就行了。
由于dp单增,所以查找i时可以用二分查找,因此时间复杂度为O(nlogn)。
举个例子,假如序列为 3 2 8 6 7 4 5 7 3,则dp数组的变化过程如下:
3
2
2 8
2 6
2 6 7
2 4 7
2 4 5
2 4 5 7
2 3 5 7
最后,f的长度达到4,因此答案为4。
注意,最后的f数组不一定是最长上升子序列的一个方案。
最长公共子序列lcs
一个数列 ,如果分别是两个或多个已知数列的子序列,且是所有符合此条件序列中最长的,则称为已知序列的最长公共子序列。
最长公共子序列英文全称The longest common subsequence,简称LCS。最长公共子序列的问题常用于解决字符串的相似度,是一个非常实用的算法,此算法是我们的必备基本功。
设序列X={x1, x2, ..., xm} 和 Y={y1, y2, ..., yn}的最长公共子序列为Z={z1, z2, ..., zk},则:
(1) 若xm=yn,则zk=xm=yn,且zk-1是xm-1和yn-1的最长公共子序列;
(2) 若xm≠yn 且 zk≠xm,则Z是xm-1和Y的最长公共子序列;
(3) 若xm≠yn 且 zk≠yn,则Z是X和yn-1和Y的最长公共子序列。
由此可见,2个序列的最长公共子序列包含了这2个序列的前缀的最长公共子序列。因此最长公共子序列具有最优子结构性质。
用dp[i][j]记录最长公共子序列的长度。其中,Xi={x1, x2, ..., xi} , Yj={y1, y2, ..., yj}。当i=0或j=0时,空序列是Xi和Yj的最长公共子序列。此时,dp[i][j]=0。状态转移方程如下:
代码如下:
计算最长公共子序列的长度:
void lcslength(char x[],char y[], int m,int n){
int L[m][n],i,j;
for(i=0;i<=m;i++) L[i][0]=0;
for(i=0;i<=n;i++) L[0][i]=0;
for(i=1;i<=m;i++)
for(j=1;j<=n;j++){
if(x[i]==y[j]) L[i][j] = L[i-1][j-1] + 1;
else L[i][j] = max{ L[i-1][j], L[i][j-1] };
}
return L[m][n];
}
【例题】最长公共子序列
一个给定序列的子序列是在该序列中删去若干元素后得到的序列。确切地说,若给定序列X=《x1,x2,……,xm》,则另一序列Z=《z1,z2,……,zk》是X的子序列是指存在一个严格递增的下标序列 《i1,i2,……,ik》,使得对于所有j=1,2,……,k有:
Zj=Xij
例如,序列Z=《B,C,D,B》是序列X=《A,B,C,B,D,A,B》的子序列,相应的递增下标序列为《2,3,5,7》。给定两个序列X和Y,当另一序列Z既是X的子序列又是Y的子序列时,称Z是序列X和Y的公共子序列。例如,若X=《A,B,C,B,D,A,B》和Y=《B,D,C,A,B,A》,则序列《B,C,A》是X和Y的一个公共子序列,序列《B,C,B,A》也是X和Y的一个公共子序列。而且,后者是X和Y的一个最长公共子序列,因为X和Y没有长度大于4的公共子序列。
给定两个序列X=《x1,x2,……,xm》和Y=《y1,y2,……,yn》,要求找出X和Y的一个最长公共子序列。
【输入格式】
共有两行,每行为一个由大写字母构成的长度不超过200的字符串,表示序列X和Y。
【输出格式】
一个非负整数,表示所求得的最长公共子序列的长度,若不存在公共子序列,则仅输出一个整数0。
【样例输入】
ABCBDAB
BDCABA
【样例输出】
4
最长公共子序列的长度很容易求出,那我们如何求出最长公共子序列呢?
例如字符串bacdbd和字符串dbcbadb,我们利用下图来说明:
下表中的数据为dp[i][j]的值:
从表中找出最长公共子序列的方法:
(1) 从(m,n)到(0,0);
(2) 若当前格与左边一格相同,则画“←”;
若当前格与上边一格相同,则画“↑”;
若以上两者都不符合,从当前格到左上格画坐上箭头。
(3) 从当前格向箭头方向前进一格,对此格进行
(4) 从(m,n)到(0,0)的不同路径中,“ ”相对应的格的元素构成最长公共子序列。
对于上面的例题,如果输入:
ABCBDAB
BDCABA
输出:
4
BCBA
如果只需要计算最长公共子序列的长度,则算法的空间需求可大大减少。事实上,在计算dp[i][j]时,只用到数组dp的第i行和第i-1行。因此,用2行的数组空间就可以计算出最长公共子序列的长度。进一步的分析还可将空间需求减至O(min(m,n))。
思考题:如何对程序进行改正,作为思考题。