每天一道算法题(5)——求2个字符串的最长公共子序列和最长公共子字符串

       题目:如果字符串一的所有字符按其在字符串中的顺序出现在另外一个字符串二中,则字符串一称之为字符串二的子串。注意,并不要求子串(字符串一)的字符必须连续出现在字符串二中。请编写一个函数,输入两个字符串,求它们的最长公共子串,并打印出最长公共子串。
       例如:输入两个字符串BDCABA 和ABCBDAB,字符串BCBA 和BDAB 都是是它们的最长公共子串,则

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


1.思路

         注意最长子序列并不考虑连续性。对于序列str1,str2,设长度为m,n。建立辅助矩阵 c[m][n],定义c[i][j]为子串str1[0-i]与子串str2[0-j]的最长子序列的长度,则c[m][n]的元素满足如下关系:


       即假设求两字符串Xm ={x0, x1,…xm-1}和Yn={y0 ,y1,…,yn-1}的LCS,如果xm-1=yn-1,那么只需求得Xm-1 和Yn-1 的LCS,并在其后添加xm-1(yn-1)即可;如果xm-1≠yn-1,我们分别求得Xm-1 和Y 的LCS 和Yn-1 和X 的LCS,并且这两个LCS 中较长的一个为X 和Y 的LCS。

       首先可以递归的求解。但是考虑到递归过程中的重复计算问题,此处使用辅助矩阵的方法。定义方向矩阵direction[m][n]. 由上叙,求取c[i,j]可能从c[i-1,j-1] 、c[i,j-1]或者c[i-1,j]三个方向计算得到,分别定义方向0,-1,1。

       对direction矩阵反向遍历,当derection[i][j]==1时,str1[i]或str2[j]即为匹配字符之一。


2.代码

       时间复杂度O(m*n),空间复杂度为O(m,n)

#include<iostream>
#include<stack>
#include"string"
using namespace std;

//使用递归计算出子序列长度并输出子序列
bool getSubsequence(int **b,const string s,stack<char>& sub,int i,int j){
     if(!b||(i*j)==0)
	      return false;
	 if(b[i][j]==0){
		  sub.push(s[j-1]);
	      getSubsequence(b,s,sub,i-1,j-1);//left-up
	 }
	 else if(b[i][j]==1)
	      getSubsequence(b,s,sub,i-1,j);//up
	 else
	     getSubsequence(b,s,sub,i,j-1);//left
		  
	return true;
}


bool LCSubsequence(const string  str1, const string str2)  
{  
    int length1,length2;  
    length1 = str1.size();  
    length2 = str2.size(); 
    if(!length1||!length2)	
	    return false;
	    
  
   
    int **c = new int*[length1+1];  
    int **direction=new int*[length1+1];//记录搜索方向
    for(int i = 0; i < length1+1; i++){  
        c[i] = new int[length2+1]; 
	c[i][0]=0;        //第0列初始化为0 
	direction[i]=new int[length2+1];
    }  
    for(int j = 0; j<length2+1; j++)  
        c[0][j]=0;        //第0行初始化为0 


	
    //获得序列搜索路径存入direction
    for(int i = 1; i < length1+1; i++)  
    {  
        for(int j = 1; j < length2+1; j++)  
        {  
            if(str1[i-1]==str2[j-1]) 
            {  
                c[i][j]=c[i-1][j-1]+1;  
                direction[i][j]=0;          
            }  
            else if(c[i-1][j]>c[i][j-1])//1 means up
            {  
                c[i][j]=c[i-1][j];  
                direction[i][j]=1;  
            }  
            else//-1 measns left  
            {  
                c[i][j]=c[i][j-1];  
                direction[i][j]=-1;  
            }  
        }  
    }
	
	
	stack<char> sub;
	//getSubsequence(direction,str2,sub,length1,length2);//使用递归计算出子序列长度并输出子序列
	int i=length1,j=length2;
	while(i*j!=0){
		if(str1[i-1]==str2[j-1]){
			sub.push(str1[i-1]);
			i--;
			j--;
		}
		else if(direction[i-1][j]>direction[i][j-1])
			i--;
		else
			j--;

       }
	cout<<"length  of LCS is "<< sub.size()<<endl;
	cout<<"subsequence is----";
	while(!sub.empty()){
	  cout<<sub.top()<<" ";
	  sub.pop();
	}
	cout<<endl;


	
	//释放内存
	for(int i = 0; i < length1+1; i++){ 
         delete []c[i];
         delete []direction[i]	;	 
	}
	delete []c;
	delete []direction;

       return true;	
}

void main(){
    const string s1("BDABA");
    const string s2("ABMBCDATB");
    LCSubsequence(s1,s2);
}


3.求最长公共子字符串

     子字符串需要考虑字符连续问题。由于连续,故只有“左上”搜索方向,只需考虑如何计算c[i][j]即可。此时,若str1[i]==str2[j], c[i][j]=c[i-1][j-1]+1否则等于0。只需考虑最大的c[i][j]按斜对角线输出即可:

#include<iostream>
#include<stack>
#include"string"
using namespace std;

bool LCSubstring(const string  str1, const string str2)  
{  
    int length1,length2;  
    length1 = str1.size();  
    length2 = str2.size(); 
    if(!length1||!length2)	
	    return false;
	    
  
   
    int **c = new int*[length1+1];  
    for(int i = 0; i < length1+1; i++){  
        c[i] = new int[length2+1]; 
	c[i][0]=0;        //第0列初始化为0 
    }  
    for(int j = 0; j<length2+1; j++)  
        c[0][j]=0;        //第0行初始化为0 


	
    int maxI=0;//当前最长公共子字符串的位置
    int length=0;//最长公共子字符串的长度
    for(int i = 1; i < length1+1; i++)  
    {  
        for(int j = 1; j < length2+1; j++)  
        {  
            if(str1[i-1]==str2[j-1])   
                c[i][j]=c[i-1][j-1]+1;           
            else  
                c[i][j]=0;

			if(c[i][j]>length){
				length=c[i][j];
				maxI=i;
			}
        }  
    }
	

   cout<<"最长公共子字符串为---";
   for(int i=0;length&&i<length;i++)
	  cout<<str1[maxI-length+i];
   cout<<endl;


	
   //释放内存
   for(int i = 0; i < length1+1; i++)
        delete []c[i];
   delete []c;

    return true;	
}

void main(){
    const string s1("21232523311324");
    const string s2("1523412321");
    LCSubstring(s1,s2);
}


    




参考

      1.程序员面试100题之六:最长公共子序列



原文地址:https://www.cnblogs.com/engineerLF/p/5393039.html