【动态规划】LCS算法:求两字符串最大公共子序列/删除字符使成为回文串

来源:互联网 发布:怎么面试美工 编辑:程序博客网 时间:2024/06/05 07:42

问题描述:给定一个字符串s,你可以从中删除一些字符,使得剩下的串是一个回文串。如何删除才能使得回文串最长呢?输出需要删除的字符个数。

例如:输入:google 输出:2

思路:回文串通常可以用逆序的方式寻找思路。例如字符串google逆序后elgoog,字符串alibaba逆序后ababila,可以发现求回文串的问题可以转换成求两个字符串的最大公共子序列的问题(序列可以不连续)。

需要删除的长度 = 字符串的长度 - 字符串与逆序字符串的最大公共子序列的长度

问题描述:求两个字符串的最大公共子序列(LCS)(序列可以不连续)

例如:alibaba和ababila的最大公共子序列为ababa

思路:对于字符串a和字符串b,长度分别为m,n,先考虑他们的最后一个字符。

(1)如果相等,说明最后一个字符一定可以是最大公共子序列的最后一位(这里使用可以的意思是:有多解),那么我们可以先丢弃这最后一位,求解字符串a.substr(0, m-2)和b.substr(0, n-2)的最大公共子序列。a.substr(x,y)表示截取字符串a的x到y下标之间的部分

(2)如果不相等,则最大子序列一定在一下两者之间:a.substr(0, m-2) 和 b.substr(0, n-1)(丢弃a的最后一位)a.substr(0, m-1) 和 b.substr(0, n-2)(丢弃b的最后一位)

递归方法

这样就可以用递归求解(只求解长度)。

int LCS_Length(string str1, string str2, int n1, int n2){<span style="white-space:pre"></span>if(n1==0 || n2==0)<span style="white-space:pre"></span>return 0;<span style="white-space:pre"></span>if(str1[n1-1]==str2[n2-1])<span style="white-space:pre"></span>return 1+LCS_Length(str1.substr(0,n1-1),str2.substr(0,n2-1),n1-1,n2-1);<span style="white-space:pre"></span>else<span style="white-space:pre"></span>{<span style="white-space:pre"></span>int a=LCS_Length(str1,str2.substr(0,n2-1),n1,n2-1);<span style="white-space:pre"></span>int b=LCS_Length(str1.substr(0,n1-1),str2,n1-1,n2);<span style="white-space:pre"></span>return max(a,b);<span style="white-space:pre"></span>}}

动态规划法

但是每次递归,都要对前面的子字符串的最大公共子序列重新计算一遍,效率很低。因此可以用动态规划的方法。

动态规划法利用了一个矩阵vec,矩阵中第i行第j列表示,字符串前i个字符与字符串前j个字符的最大公共子序列,因此第0行与第0列全部元素为0。如图所示。

其余元素均可根据其上方左上方左方的三个数推断出来。规则来自上述的思路(1)(2):判断当前vec[i][j]对应的行列字符是否相等,如果相等,则取左上方加1  vec[i][j] = vec[i-1][j-1] + 1;如果不等,则取上方vec[i-1][j]和 左方 vec[i][j-1]的最大值。

例如:图2中红色的2:判断行列字符都是b,因此为左上角1+1得到2;图2中绿色的3:判断行列字符b和a不等,取上方和左方的最大值3.

   

整个矩阵求出之后,右下角的元素值就是字符串a和b的最大公共子序列的长度了。

接下来,从右下角开始回溯,以求最大公共子序列

根据上文的推到思路可以找到回溯思路,从右下角开始

(1)如果当前元素等于其左上角加1,那么当前元素行列相等,是公共子序列中的一个,保存下来。

(2)如果当前元素来自上方或左方,当前元素不保存。

按照此思路,从5开始,形成如图所示的回溯路线,只有斜向上的箭头对应的行元素纳入最大公共子序列,求得最大公共子序列为ababa。


完整代码:

#include <iostream>#include <string>#include <algorithm>#include <vector>using namespace std; int LCS_Length(string str1,string str2,vector<vector<int>> &vec)//求长度和LCS矩阵{int i,j;int n1=str1.size();int n2=str2.size();    for(i=0;i<n2+1;i++)//第0行置0        vec[0][i] = 0;    for(i= 0;i<n1+1;i++)//第0列置0        vec[i][0] = 0;    for(i=1;i<=n1;i++)    {        for(j=1;j<=n2;j++)        {            if(str1[i-1]==str2[j-1])                vec[i][j]=vec[i-1][j-1]+1;            else                vec[i][j]=max(vec[i][j-1],vec[i-1][j]);        }    }    return vec[n1][n2];}void LCS(string str,vector<vector<int>> vec,int i,int j)//回溯,求LCS{      if(i==-1||j==-1)          return;      if(vec[i+1][j+1]==vec[i][j+1])           LCS(str,vec,i-1,j);        else if(vec[i+1][j+1]==vec[i+1][j])          LCS(str,vec,i,j-1);      else  {        LCS(str,vec,i-1,j-1);cout<<str[i];    }  }int main(){    string str;    while(cin>>str){int lcslen;        int len = str.size();        string str_reverse = str;        reverse(str_reverse.begin(),str_reverse.end());vector<vector<int>> vec(len+1);//创建动态规划矩阵for(int i = 0; i < len+1; ++i)vec[i].assign(len+1,-1);        lcslen=LCS_Length(str,str_reverse,vec);cout<<"最大公共子序列长度:"<<lcslen<<endl;cout<<"构成回文串要删除的字符个数:"<<len-lcslen<<endl;cout<<"最大公共子序列(即构成的回文串)为:";LCS(str,vec,len-1,len-1);cout<<endl;    }    return 0;}


0 0