最长公共子序列

来源:互联网 发布:鲁豫采访谁谁倒霉知乎 编辑:程序博客网 时间:2024/06/18 15:58

               子序列:一个序列A=a1,a2,a3...,an,中删除若干项得到的新的序列即为A的子序列。

                                例1:序列1,6,2,3,7,5的一个子序列为1,2,3,5。

                                对于一个长度为n的序列,其子序列共有2的n次方个,其非空子序列共有2的n-1次方个

      公共子序列:同时是两个或多个序列的子序列。

                                例2:序列1,3,5,4,2,6,8,7和序列1,8,6,7,5的一个公共子序列为1,6,7

最长公共子序列:在上面的例2中,1,5也是两个序列的公共子序列,以及1或8等等这些单个数字也是它

                                们的一个子序列。最长子序列即指在这些子序列中最长的,1,6,7,或1,8,7,即为这两个序

                                列的最长公共子序列(LCS)。



分析:

我们定义这样一个dp数组,dp[i][j]表示a1,a2...,ai和b1,b2,...,bj的最长公共子序列长度。

循环枚举i和j的值,


当ai==bj。

假设i=4,j=3,即在求序列a1,a2,a3,a4与序列b1,b2,b3时发现a4=b3,则这两个序列的最长公共子序列长度可

以由它的前一个状态的结果加1求得,即:a1,a2,a3与序列b1,b2的最长公共子序列长度+1

dp[i][j]=dp[i-1][j-1]+1


当ai!=bj时

假设i=4,j=3,即在求序列a1,a2,a3,a4与序列b1,b2,b3时发现a4!=b3,因为a4!=b3所以有前2个状态。

状态1:a1,a2,a3,a4与b1,b2的最长公共子序列长度。

状态2:a1,a2,a3,与b1,b2,b2的最长公共子序列长度。

a4!=b3所以不能由前面状态的结果加1求得,而是直接等于前面状态的结果,右因为是求最长的,所以等于

状态1与状态2中最长的那个。即:dp[i][j]=Math.max(dp[i-1][j], dp[i][j-1])。


最后求出数组dp中的最大值即为这两个序列的最大公共子序列长度


import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner sc= new Scanner(System.in);    //System.out.println("请输入序列A:");String a=sc.nextLine();//System.out.println("请输入序列B:");String b=sc.nextLine();int dp[][]=new int[a.length()+1][b.length()+1];//多开一行一列,不用0行0列以防止数组越界int max=0;//记录最终结果for(int i=1;i<=a.length();i++){//注意i和j都是从1开始分别到两个序列的长度结束for(int j=1;j<=b.length();j++){if(a.charAt(i-1)==b.charAt(j-1))//i和j都是从1开始的,所以减1防止数组越界dp[i][j]=dp[i-1][j-1]+1;//ai=bj的情况elsedp[i][j]=Math.max(dp[i-1][j], dp[i][j-1]);//ai!=bj,则等于前两个状态中值最大的那个max=max>dp[i][j]?max:dp[i][j];//每更新依次dp数组中的值都比较一次,用来记录dp数组中的最大值}}System.out.println(max);}}


到目前为止我们只是求出了最大公共子序列的长度,并没有达到求出整个子序列的要求,其实求整个子

序列只需要对每个状态添加一个标记。前面说到对状态dp[i][j]有三个分支:

当ai==bj时

dp[i][j]=dp[i-1][j-1]      //分支1

当a1!=bi&&dp[i-1][j]>dp[i][j-1]时

dp[i][j]=dp[i-1][j]         //分支2

当a1!=bi&&dp[i][j-1]>dp[i-1][j]时

dp[i][j]=dp[i][j-1]         //分支3

用标志数组TAG记录每个状态由哪一个分支得到的,最后根据标志数组得到最大公共子序列。

例如当TAG[i][j]==1时,表示ai==bj,此时将ai记录到最终的结果字符串中



import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner sc= new Scanner(System.in);    //System.out.println("请输入序列A:");String a=sc.nextLine();//System.out.println("请输入序列B:");String b=sc.nextLine();int dp[][]=new int[a.length()+1][b.length()+1];//多开一行一列,不用0行0列以防止数组越界int tag[][]=new int[a.length()+1][b.length()+1];//标志数组,用来记录进行了哪种操作for(int i=1;i<=a.length();i++){//注意i和j都是从1开始分别到两个序列的长度结束for(int j=1;j<=b.length();j++){if(a.charAt(i-1)==b.charAt(j-1)){//i和j都是从1开始的,所以减1防止数组越界dp[i][j]=dp[i-1][j-1]+1;//ai=bj的情况tag[i][j]=1;//分支1}else{    if(dp[i-1][j]>dp[i][j-1]){    dp[i][j]=dp[i-1][j];    tag[i][j]=2;//分支2    }else{    dp[i][j]=dp[i][j-1];    tag[i][j]=3;//分支3    }}}}int lenA=a.length(),lenB=b.length();String result=new String();//用来保存结果while(lenA>0&&lenB>0){if(tag[lenA][lenB]==1){//分支1,ai==bjresult+=a.charAt(lenA-1);//lenA、lenB都进行自减lenA--;lenB--;}else if(tag[lenA][lenB]==2)//分支2,dp[i][j]继承了上一个状态a1,a2...,a(i-1)与b1,b2...,bj的最长公共子序列长度lenA--;//只需lenA--else                       //分支2,dp[i][j]继承了上一个状态a1,a2...,ai与b1,b2...,b(j-1)的最长公共子序列长度lenB--;//只需lenB--}//由于是由后向前几率最长公共子序列,所以需要对result进行一次翻转(StringBuffer对象.revers:对字符缓冲区中的内容进行翻转)System.out.println(new StringBuffer(result).reverse());}}

翻转:将字符串result保存到字符串缓冲区中

            StringBuffer s=new StringBuffer(result);

            再调用SringBuffer中的revers()方法,得到的结果即为result的翻转

            s.revers();







0 0