最长公共子序列

最长公共子序列

(本文仅为笔者的个人学习笔记,如有不当之处恳请各位读者指正)

子序列:一个序列,中删除若干项得到的新的序列即为A的子序列。例1:序列1,6,2,3,7,5的一个子序列为1,2,3,5。对于一个长度为n的序列,其子序列共有2的n次方个,其非空子序列共有

公共子序列:同时是两个或多个序列的子序列。例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]表示A中的一个子序列和B中的一个子序列的最长公共子序列长度(其中:)。循环枚举i和j的值,


  • 时。

    假设i=4,j=3,即在求序列与序列的最长公共子序列时若发现,则这两个序列的最长公共子序列长度可以由序列与序列的最长公共子序列的长度加1求得,即:


    假设i=4,j=3,即在求序列与序列时若发现,此时序列与序列的最长公共子序列的长度为:

                             序列与序列的最长公共子序列的长度

                             或序列与序列的最长公共子序列的长度

由于是求最长子序列,自然是取最长的一个。即: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的情况
				else
					dp[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]有三个分支:

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

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

        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==bj
				result+=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
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值