最长公共子序列问题--动态规划


给定两个字符串str1和str2,返回两个字符串的最长公共子序列。

【题目】
    给定两个字符串str1和str2,返回两个字符串的最长公共子序列。
【举例】 
    str1="1A2C3D4B56",str2="B1D23CA45B6A""123456"或者"12C4B6"都是最长公共子序列,返回哪一个都行。


【解答】
本题是非常经典的动态规划问题,先来介绍求解动态规划表的过程。
如果str1的长度为M,str2的长度为N,生成大小为M×N的矩阵dp,行数为M,列数为N。
dp[i][j]的含义是str1[0..i]与str2[0..j]的最长公共子序列的长度。
从左到右,再从上到下计算矩阵dp。

1.矩阵dp第一列即dp[0..M-1][0],dp[i][0]的含义是str1[0..i]与str2[0]的最长公共子序列长度。
str2[0]只有一个字符,所以dp[i][0]最大为1。如果str1[i]==str2[0],令dp[i][0]=1,一旦dp[i][0]被设置为1,之后的dp[i+1..M-1][0]也都为1。
比如,str1[0..M-1]=”ABCDE”,str2[0]=”B”。str1[0]为”A”,与str2[0]不相等,
所以dp[0][0]=0。str1[1]为”B”,与str2[0]相等,所以str1[0..1]与str2[0]的最长公共子序列为”B”,
令dp[1][0]=1。之后的dp[2..4][0]肯定都是1,
因为str[0..2]、str[0..3]和str[0..4]与str2[0]的最长公共子序列肯定有”B”。

2.矩阵dp第一行即dp[0][0..N-1]与步骤1同理,如果str1[0]==str2[j],则令dp[0][j]=1,
一旦dp[0][j]被设置为1,之后的dp[0][j+1..N-1]也都为1。


3.对其他位置(i,j),dp[i][j]的值只可能来自以下三种情况:
● 可能是dp[i-1][j],代表str1[0..i-1]与str2[0..j]的最长公共子序列长度。
比如,str1=”A1BC2”,str2=”AB34C”。str1[0..3]
(即”A1BC”)与str2[0..4](即”AB34C”)的最长公共子序列为”ABC”,
即dp[3][4]为3。str1[0..4](即”A1BC2”)与str2[0..4](即”AB34C”)
的最长公共子序列也是”ABC”,所以dp[4][4]也为3。
● 可能是dp[i][j-1],代表str1[0..i]与str2[0..j-1]的最长公共子序列长度。
比如,str1=”A1B2C”,str2=”AB3C4”。
str1[0..4](即”A1B2C”)与str2[0..3](即”AB3C”)
的最长公共子序列为”ABC”,即dp[4][3]为3。
str1[0..4](即”A1B2C”)与str2[0..4](即”AB3C4”)的最长公共子序列也是”ABC”,
所以dp[4][4]也为3。
● 如果str1[i]==str2[j],还可能是dp[i-1][j-1]+1。
比如str1=”ABCD”,str2=”ABCD”。str1[0..2](即”ABC”)与str2[0..2](即”ABC”)
的最长公共子序列为”ABC”,即dp[2][2]为3。因为str1[3]==str2[3]==”D”,
所以str1[0..3]与str2[0..3]的最长公共子序列是”ABCD”。
这三个可能的值中,选最大的作为dp[i][j]的值。

    public static String getdp(char[] str1,char[] str2){
        if(str1==null||str2==null||str1.length==0||str1.length==0){
            return "";
        }
        int m=str1.length;
        int n=str2.length;
        //dp[i][j]对应str1[0...i]和str2[0...j]的最长公共子序列
        int[][] dp=new int [m][n];
        dp[0][0]=(str2[0]==str1[0])?1:0;
        //初始化第一行
        for (int i = 1; i < n; i++) {
            if(str2[i]==str1[0]||dp[0][i-1]==1){
                dp[0][i]=1;
            }else{
                dp[0][i]=0;
            }
        }
        //初始化第一列
        for (int i = 1; i < m; i++) {
            if(str1[i]==str2[0]||dp[i-1][0]==1){
                dp[i][0]=1;
            }else{
                dp[i][0]=0;
            }
        }
        //初始化dp[i][j]
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[i][j]=Math.max(dp[i][j-1], dp[i-1][j]);
                if(str1[i]==str2[j]){
                    //str1[i]==str2[j],对应的dp[i][j]应该增1,str1[i]就是公共子序列的一部分
                    dp[i][j]=Math.max(dp[i][j],dp[i-1][j-1]+1);
                }
            }
        }
        char[] res=new char[dp[m-1][n-1]];//dp[m-1][n-1]对应最长公共子序列长度
        int index=res.length-1;
        m--;
        n--;
        //从下往上获取最长公共子序列
        while(index>=0) {
                if(m>0&&dp[m][n]==dp[m-1][n]){
                    m--;
                }else if(n>0&&dp[m][n]==dp[m][n-1]){
                    n=n-1;
                }else{
                    res[index--]=str1[m];
                    m=m-1;
                    n=n-1;
                }   
            }

        return String.valueOf(res);
    }

测试一下:

    public static void main(String[] args) {
        String s1="1a2c3d4b56";
        String s2="b1d23ca45b6a";
        char[] str1=s1.toCharArray();
        char[] str2=s2.toCharArray();
        System.out.println(getdp(str1, str2));
    }

12c4b6

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值