最长01串 java_最长子串(动态规划)

本文介绍了如何使用动态规划解决Java编程中求解两个字符串的最长公共子串问题。通过分析问题性质,给出递归解法,并转换为循环求解,提高效率。同时提供了C++实现的代码示例。
摘要由CSDN通过智能技术生成

题目:如果字符串一的所有字符按其在字符串中的顺序出现在另外一个字符串二中,

则字符串一称之为字符串二的子串。

注意,并不要求子串(字符串一)的字符必须连续出现在字符串二中。

请编写一个函数,输入两个字符串,求它们的最长公共子串,并打印出最长公共子串。

例如:输入两个字符串BDCABA和ABCBDAB,字符串BCBA和BDAB都是是它们的最长公共子串,

则输出它们的长度4,并打印任意一个子串。

分析:求最长公共子串(Longest CommonSubsequence, LCS)是一道非常经典的动态规划题。

以下分析参见另外的一篇博文。

步骤一、描述一个最长公共子序列

先介绍LCS问题的性质:记Xm={x0, x1,…xm-1}和Yn={y0,y1,…,yn-1}为两个字符串,

并设Zk={z0,z1,…zk-1}是X和Y的任意一个LCS,则可得出3条性质:

1.      如果xm-1=yn-1,那么zk-1=xm-1=yn-1,并且Zk-1是Xm-1和Yn-1的一个LCS;

2.      如果xm-1≠yn-1,那么当zk-1≠xm-1时,Z是Xm-1和Y的LCS;

3.      如果xm-1≠yn-1,那么当zk-1≠yn-1时,Z是X和Yn-1的LCS;

下面简单证明一下由上述相应条件得出的这些性质:

1.      如果zk-1≠xm-1,那么我们可以把xm-1(yn-1)加到Z中得到Z’,这样就得到X和Y的一个长度为k+1的公共子串Z’。

这就与长度为k的Z是X和Y的LCS相矛盾了。因此一定有zk-1=xm-1=yn-1。

既然zk-1=xm-1=yn-1,那如果我们删除zk-1(xm-1、yn-1)得到的Zk-1,Xm-1和Yn-1,显然Zk-1是Xm-1和Yn-1的一个公共子串,现在我们证明Zk-1是Xm-1和Yn-1的LCS。用反证法不难证明。假设有Xm-1和Yn-1有一个长度超过k-1的公共子串W,那么我们把加到W中得到W’,那W’就是X和Y的公共子串,并且长度超过k,这就和已知条件相矛盾了。

2.      还是用反证法证明。假设Z不是Xm-1和Y的LCS,则存在一个长度超过k的W是Xm-1和Y的LCS,那W肯定也X和Y的公共子串,而已知条件中X和Y的公共子串的最大长度为k。矛盾。

3.      证明同2。

步骤二、一个递归解

根据上面的性质,我们可以得出如下的思路:

求两字符串Xm={x0, x1,…xm-1}和Yn={y0,y1,…,yn-1}的LCS,

如果xm-1=yn-1,那么只需求得Xm-1和Yn-1的LCS,并在其后添加xm-1(yn-1)即可(上述性质1);

如果xm-1≠yn-1,我们分别求得Xm-1和Y的LCS和Yn-1和X的LCS,并且这两个LCS中较长的一个为X和Y的LCS(上述性质2、3)。

根据上述结论,可得到以下公式,

如果我们记字符串Xi和Yj的LCS的长度为c[i,j],我们可以递归地求c[i,j]:

/     0                              if i<0 or j<0

c[i,j]=         c[i-1,j-1]+1                   if i,j>=0 and xi=xj

/      max(c[i,j-1],c[i-1,j]          if i,j>=0 and xi≠xj

上面的公式用递归函数不难求得。自然想到Fibonacci第n项(本微软等100题系列V0.1版第19题)问题的求解中可知,

直接递归会有很多重复计算,所以,我们用从底向上循环求解的思路效率更高。

为了能够采用循环求解的思路,我们用一个矩阵(参考下文文末代码中的LCS_length)保存下来当前已经计算好了的c[i,j],

当后面的计算需要这些数据时就可以直接从矩阵读取。

另外,求取c[i,j]可以从c[i-1,j-1] 、c[i,j-1]或者c[i-1,j]三个方向计算得到,

相当于在矩阵LCS_length中是从c[i-1,j-1],c[i,j-1]或者c[i-1,j]的某一个各自移动到c[i,j],

因此在矩阵中有三种不同的移动方向:向左、向上和向左上方,其中只有向左上方移动时才表明找到LCS中的一个字符。

于是我们需要用另外一个矩阵(参考下文文末代码中的LCS_direction)保存移动的方向。

然后下面也是参见其博文后,修改部分所得到的C++实现源码:

// 动态规划_最大子串.cpp : 定义控制台应用程序的入口点。

//

#include "stdafx.h"

#include

#include

using namespace std;

enum decreaseDir{kInit=0,kLeft,kUp,kLeftUp};

void LCS_Print(int** LCS_dirction,string pStr1,string pStr2,int row,int col);

int LCS(string pStr1,string pStr2)

{

//if(!pStr1||!pStr2)return 0;

int length1=pStr1.length();

int length2=pStr2.length();

if(!length1||!length2)return 0;

int i,j;

int** LCS_length;

LCS_length=(int**)(new int[length1]);

for(i=0;i

LCS_length[i]=(int*)new int[length2];

for(i=0;i

for(j=0;j

LCS_length[i][j]=0; //初始化length matrix

int** LCS_dirction;

LCS_dirction=(int**)(new int[length1]);

for(i=0;i

LCS_dirction[i]=(int*)new int[length2];

for(i=0;i

for(j=0;j

LCS_dirction[i][j]=kInit; //初始化dirction matrix

for(i=0;i

{

for(j=0;j

{

if(i==0||j==0)

{

if(pStr1[i]==pStr2[j])

{

LCS_length[i][j]=1;

LCS_dirction[i][j]=kLeftUp;

}

else LCS_length[i][j]=0;

}

else if(pStr1[i]==pStr2[j])

{

LCS_length[i][j]=LCS_length[i-1][j-1]+1;

LCS_dirction[i][j]=kLeftUp;

}

else if(LCS_length[i-1][j]>LCS_length[i][j-1])

{

LCS_length[i][j]=LCS_length[i-1][j];

LCS_dirction[i][j]=kUp;

}

else

{

LCS_length[i][j]=LCS_length[i][j-1];

LCS_dirction[i][j]=kLeft;

}

}

}

LCS_Print(LCS_dirction,pStr1,pStr2,length1-1,length2-1);

return LCS_length[length1-1][length2-1];

}

void LCS_Print(int** LCS_dirction,string pStr1,string pStr2,int row,int col)

{

//if(pStr1==NULL||pStr2==NULL)return;

int length1=pStr1.length();

int length2=pStr2.length();

if(length1==0||length2==0||!(row

if(LCS_dirction[row][col]==kLeftUp)

{

if(row>0&&col>0)

LCS_Print(LCS_dirction,pStr1,pStr2,row-1,col-1);

printf("%c",pStr1[row]);

}

else if(LCS_dirction[row][col]==kLeft)

{

if(col>0)

LCS_Print(LCS_dirction,pStr1,pStr2,row,col-1);

}

else if(LCS_dirction[row][col]==kUp)

{

if(row>0)

LCS_Print(LCS_dirction,pStr1,pStr2,row-1,col);

}

}

int _tmain(int argc, _TCHAR* argv[])

{

string str1="BDCABA";

//char str1[]={'B','D','C','A','B','A'};

string str2="ABCBDAB";

//char str2[]={'A','B','C','B','D','A','B'};

cout<

int Length=LCS(str1,str2);

cout<

int k=0;

cin>>k;

return 0;

}

程序的运行截图:

0fbc1a848320cee5250c0b9cb4b99c9e.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值