动态规划5:LCS最长公共子序列问题

题目:给定两个字符串A和B,返回两个字符串的最长公共子序列的长度。例如,A="1A2C3D4B56”,B="B1D23CA45B6A”,”123456"或者"12C4B6"都是最长公共子序列。给定两个字符串A和B,同时给定两个串的长度n和m,请返回最长公共子序列的长度。保证两串长度均小于等于300。

测试样例:"1A2C3D4B56",10,"B1D23CA45B6A",12返回:6

思路:所谓LCS问题就是指LongestCommonSequence最长公共子序列问题,所谓子序列并不是连续子序列,只要是在一个序列中的子序列就可以。给定2个字符串,要求找出它们的最长的公共子序列,题目输入的是2个String类型的字符串,要求返回这2个字符串的最长公共子序列的长度。如果不使用任何套路算法,这道题目非常复杂困难,如果使用暴力的搜索那时间复杂度一定会爆表,因此必然需要使用固定的策略来解决问题。在编程题目中,面对这种非常复杂的问题,一般就是使用动态规划来解决。

动态规划需要将问题分解降维,动态规划有自己固定的套路,按照套路来就可以解决问题,但是最关键的还是对于问题本身的理解,即要先想好这个问题怎么处理,内部的关联是怎么样的,然后再建立dp[]矩阵通过动态规划的固定方法来求解问题。一般通过建立dp[][]矩阵来解决问题,求解dp[i][j]时依赖于计算dp[i-1][j]和dp[i][j-1],因此要找出dp[i][j]与dp[i-1][j]和dp[i][j-1]之间的依赖关系,即如何根据dp[i-1][j]和dp[i][j-1]求得dp[i][j]。

本题中的逻辑非常精密,需要好好理解。对于任意dp[i][j]表示字符串String1[0~i]和字符串String2[0~j]的最长公共子序列的长度,如果String1[i]与String[j]相等,它的值只可能来自于3种情况;如果String1[i]与String[j]不相等,那么只可能来自于2种情况:

当String1[i]与String[j]不相等时:

例如String2=ABCDEFG;String1=ABCF,由于最后一个字符不相同,那么显然他们的最长公共字符串不可能既包含String1的最后一个字符F又包含String1的最后一个字符G,最多可以包含其中1个或者2个都不包含。

如果最长公共字符串包含G,那么必定不包含F,于是此时String2=ABCDEFG;String1=ABCF的最大公共字符串必定来自String2=ABCDEFG;String1=ABC;

如果最长公共字符串包含F,那么必定不包含G,于是此时String2=ABCDEFG;String1=ABCF的最大公共字符串必定来自String2=ABCDEF;String1=ABCF;

如果最长公共字符串既不包含G也不包含F,那么最大公共字符串显然也在上面的2中情形中。即几种情况下,String1增加了1个元素或者String2增加了1个元素并没有使得公共子序列变长,于是还是可以通过前面的部分字符串的最大公共子序列作为当前的最大公共子序列,于是dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1]);-----显然的,可以直观理解,由于String[i]和String[j]不同,因此最大公共子序列不包含String[i]或者String[j],来自dp[i-1][j]或者dp[i][j-1]中的较大者。

当String1[i]=String2[j]时,表明此时2个字符串的最后一个字符相同,但是这个公共字符不一定可以作为最大公共字符串的最后一个元素,因为如果String1的最后一个元素已经与String2的j之前的元素重合组成了公共字符串,那么此时这个字符不会增加到最长公共字符串的长度,同理如果String2的最后一个元素已经与String1的i之前的元素重合组成了最大公共字符串,那么这个元素也不会增加公共字符串的长度,只有当String[i]不是之前的最大公共字符串的元素,String[j]也不是之前最大公共字符串的元素时,这个字符才真正增加了最长公共字符串,使得从之前的dp[i-1][j-1]增加了1个长度,于是dp[i][j]=max{dp[i-1][j],dp[i][j-1],dp[i-1][j-1]+1}

动态规划4部曲:

①创建一个二维矩阵存放结果dp[n][m];

②求二维矩阵第1行和第1列的元素值:

第1行:dp[0][i]表示String1[0]这个字符和String2[0~i]的最大公共字符串长度,如果某个i开始相同,就设为1,之后的dp[0][i]都设为1;

第1列:dp[j][0]表示String2[0]这个字符与String1[0~j]的最大公共字符串长度,如果某个j开始相同,就将其设为1,之后的dp[j][0]都设为1;

③从i=1,j=1开始通过二重循环计算求dp[i][j]的值;

④最终dp[n-1][m-1]的值就是所求的结果值。

import java.util.*;
//最长公共子序列问题:动态规划4部曲
public class LCS {
    public int findLCS(String A, int n, String B, int m) {
        //特殊输入
        if(A==null||B==null||n<=0||m<=0) return 0;
        //将字符串转变为数组才能遍历访问
        char[] arrA=A.toCharArray();
        char[] arrB=B.toCharArray();
        //①创建动态规划数组存放结果:A放在列上,B放在行上
        int[][] dp=new int[n][m];
        //②计算第1行的dp[][]值
        int i=0;
        for(;i<m;i++){
            if(arrB[i]==arrA[0]) break;
        }
        for(;i<m;i++){
            dp[0][i]=1;
        }
        //②计算第1列的dp[][]值
        i=0;
        for(;i<n;i++){
            if(arrA[i]==arrB[0]) break;
        }
        for(;i<n;i++){
            dp[i][0]=1;
        }
        //③从上到下,从左到右计算dp[i][j]
        for(i=1;i<n;i++){
            for(int j=1;j<m;j++){
                if(arrA[i]==arrB[j]){
                    dp[i][j]=Math.max(Math.max(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1]+1);
                }else{
                    dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        //④返回结果--矩阵右下角的值就是结果
        return dp[n-1][m-1];
    }
}




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值