最长公共子序列问题--构造回文字符串
- 今天做腾讯编程题,碰到了一个构造回文字符串的问题,一开始我想到的是暴利法去解决这个问题,发现有点复杂;之后想到了这个东西有点像最长公共子序列问题。但是动态规划那个算法忘了咋回事了,上网找了找,当做复习一遍了。
- 一、什么是最长公共子序列
-
什么是最长公共子序列呢?举个简单的例子吧,一个数列S,若分别是两个或多个已知序列的子序列,且是所有符合条件序列中最长的,则S称为已知序列的最长公共子序列。举例如下,如:有两个随机数列,1 2 3 4 5 6 和 3 4 5 8 9,则它们的最长公共子序列便是:3 4 5。一直不明白:最长公共子串和最长公共子序列的区别。上网查了下,最长公共子串(Longest Common Substirng)和最长公共子序列(Longest Common Subsequence,LCS)的区别为:子串是串的一个连续的部分,子序列则是从不改变序列的顺序,而从序列中去掉任意的元素而获得新的序列;也就是说,子串中字符的位置必须是连续的,子序列则可以不必连续。
二、动态规划算法
事实上,最长公共子序列问题也有最优子结构性质。
记:
Xi = <x1,x2,x3,....xi> 即X序列的前i个字符(1<= i <= m)(前缀)
Yj = <y1,y2,y3,....yi> 即Y序列的前j个字符(1<= j <= m)(前缀)
假定Z = <z1,z2,z3,...zk>是LCS(X,Y)中的一个。
·若xm = yn(最后一个字符相同),则不难用反正法证明:该字符必是X与Y的任一最长公共子序列Z(设长度为k)的最后一个字符,即有zk = xm = yn,且显然有Zk-1∈LCS(Xm-1,Yn-1),即Z的前缀Zk-1是Xm-1与Yn-1的最长公共子序列。此时,问题化归成求Xm-1与Yn-1的LCS(LCS(X,Y))的长度等于LCS(Xm-1,Yn-1)的长度加1)。
·若xm≠yn,则亦不难用反证法证明:要么Z∈LCS(Xm-1, Y),要么Z∈LCS(X , Yn-1)。由于zk≠xm与zk≠yn其中至少有一个必成立,若zk≠xm则有Z∈LCS(Xm-1 , Y);类似的,若zk≠yn 则有Z∈LCS(X , Yn-1)。此时,问题化归成求Xm-1与Y的LCS及X与Yn-1的LCS。LCS(X , Y)的长度为:max{LCS(Xm-1 , Y)的长度, LCS(X , Yn-1)的长度}。
由于上述当xm≠yn的情况中,求LCS(Xm-1 , Y)的长度与LCS(X , Yn-1)的长度,这两个问题不是相互独立的:两者都需要求LCS(Xm-1,Yn-1)的长度。另外两个序列的LCS中包含了两个序列的前缀的LCS,故问题具有最优子结构性质考虑用动态规划法。
也就是说,解决这个LCS问题,你要求三个方面的东西:
1> LCS(Xm-1,Yn-1)+1;
2> LCS(Xm-1,Y),LCS(X,Yn-1);
3> max{LCS(Xm-1,Y),LCS(X,Yn-1)};
二、动态规划算法解LCS问题
2.1 最长公共子序列的结构
最长公共子序列的结构有如下表示:
设序列X=<x1, xm="" …,="" x2,="">和Y=<y1, …,="" yn="" y2,="">的一个最长公共子序列Z=<z1, …,="" zk="" z2,="">,则:
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的最长公共子序列;
其中Xm-1=<x1, …,="" x2,="" xm-1="">,Yn-1=<y1, …,="" y2,="" yn-1="">,Zk-1=<z1, …,="" z2,="" zk-1="">。
2.2 子问题的递归结构
由最长公共子序列问题的最优子结构性质可知,要找出Xm=<x1, xm="" …,="" x2,="">和Yn=<y1, …,="" yn="" y2,="">的最长公共子序列,可按如下方式递归的进行:
·当xm = yn时,找出Xm-1和Yn-1的最长公共子序列,然后在其尾部加上xm或yn,即可得到X和Y的一个最长公共子序列;
·当xm≠yn时,必须解两个子问题,即找出Xm-1和Y的一个最长公共子序列及X和Yn-1的一个最长公共子序列。这两个公共子序列中较长者即为X和Y的一个最长公共子序列。
由此递归结构容易看到最长公共子序列问题具有子问题重叠性质。例如,在计算X和Y的最长公共子序列时,可能要计算出X和Yn-1以及Xm-1和Y的最长公共子序列。而这两个子问题都包含一个公共子问题,即计算Xm-1和Yn-1的最长公共子序列。
与矩阵乘积最优计算次序问题类似,我们来建立子问题的最优值的递归关系。用c[i,j]记录序列Xi和Yj的最长公共子序列的长度,其中Xi=<x1, …,="" x2,="" xi="">,Yj=<y1, …,="" y2,="" yj="">。当i = 0或j = 0时,空序列是Xi和Yj的最长公共子序列,故c[i,j] = 0。其他情况下,可得递归关系如下所示:
2.3 计算最优值
直接利用上节节末的递归式,我们将很容易就能写出一个计算c[i,j]的递归算法,但其计算时间是随输入长度指数增长的。由于在所考虑的子问题空间中,总共只有O(m*n)个不同的子问题,因此,用动态规划算法自底向上地计算最优值能提高算法的效率。
计算最长公共子序列长度的动态规划算法LCS_Length(X,Y),以序列X=<x1, xm="" …,="" x2,="">和Y=<y1, …,="" yn="" y2,="">作为输入。输出两个数组c[0..m ,0..n]和b[1..m ,1..n]。其中c[i,j]存储Xi与Yj的最长公共子序列的长度,b[i,j]记录指示c[i,j]的值是由哪一个子问题的解达到的,这在构造最长公共子序列时要用到。最后,X和Y的最长公共子序列的长度记录于c[m,n]中。
由算法LCS_Length计算得到的数组b 可用于快速构造序列X=<x1, xm="" …,="" x2,="">和Y=<y1, …,="" yn="" y2,="">的最长公共子序列。首先从b[m,n]开始,沿着其中的箭头所指的方向在数组b中搜索。
·当 b[i,j]中遇到"↖"时(意味着 xi=yi是LCS的一个元素 ),表示 Xi与 Yj的最长公共子序列是由 子序列Xi-1与 Yj-1的最长公共子序列在尾部加上xi得到的子序列;
·当 b[i,j]中遇到"↑" 时,表示 Xi与 Yj的最长公共的最长公共子序列和Xi-1与 Yj的最长公共子序列 相同;
·当b[i,j]中遇到"←" 时,表示Xi与Yj的最长公共子序列和Xi与Yj-1的最长公共子序列相同;
这种方法是按照反序来查找LCS的每一个元素的。由于每个数组单元的计算花费O(1)时间,算法LCS_Length耗时O(mn)。
2.4 构造最长公共子序列
下面的算法LCS(b,X,i,j)实现根据b的内容打印出Xi与Yi的最长公共子序列。通过算法的调用LCS(b,X,length[X],length[Y]),便可打印出序列X和Y的最长公共子序列。
在LCS算法中,每一次递归调用使i或j减1,因此算法的时间复杂度为O(m+n)。
例如,设所给的两个序列为X=<a,b,c,b,d,a,b>和Y=<b,d,c,a,b,a>。由算法LCS_LENGTH和LCS计算出的结果如下图所示:
我来说明下此图(参考算法导论)。在序列X={A,B,C,B,D,A,B}和 Y={B,D,C,A,B,A}上,由LCS_LENGTH计算出的表c和b。第i行和第j列中的方块包含了c[i,j]的值以及指向b[i,j]的箭头。在c[7,6]的项4,表的右下角为X和Y的一个LCS<b,c,b,a>的长度。对于i,j>0,项c[i,j]仅依赖于是否有xi=yi,及项c[i-1,j]和c[i,j-1]的值,这几个项都在c[i,j]之前计算。为了重构一个LCS的元素,从右下角开始跟踪b[i,j]的箭头即可,这条路径标示为阴影,这条路径上的每一个“↖”对应于一个使xi=yi为一个LCS的成员的项(高亮标示)。
所以根据上述图所示的结果,程序将最终输出:“B C B A”。
- 构造回文题目
给定一个字符串s,你可以从中删除一些字符,使得剩下的串是一个回文串。如何删除才能使得回文串最长呢?
输出需要删除的字符个数。
输入描述:输入数据有多组,每组包含一个字符串s,且保证:1<=s.length<=1000.
输出描述:
对于每组数据,输出一个整数,代表最少需要删除的字符个数。
输入例子:
abcda
google输出例子:
2
2
解析:这个题目最简单的方法就是先将字符串取反,然后再将反字符串和原字符串一起找最长公共子序列,最后就得到了最长的回文字符串,最后再加减长度就行了。 -
#include<iostream> #include<string.h> #include<algorithm> using namespace std; const int maxsize=1010; int temp[maxsize][maxsize]; int get_delete_number(string &s1){ string s2(s1); reverse(s2.begin(),s2.end());//字符串反转 //反转函数之后求最长公共子序列 int len=s1.length(); memset(temp,0,sizeof temp);//对定义的字符串进行初始化 for(int i=0;i<len;i++) for(int j=0;j<len;j++){//求最长公共子序列的方法 if(s1[i]==s2[j]) temp[i+1][j+1]=temp[i][j]+1; else temp[i+1][j+1]=max(temp[i][j+1],temp[i+1][j]); } return len-temp[len][len]; } int main(){ string s; while(getline(cin,s)){ cout<<get_delete_number(s)<<endl; } }