DP之最长公共子序列

概念

    一个给定序列的子序列是指在该序列中删除若干元素后得到的序列。例如,X={ A, B, C, D, A, B},X 的子序列有 { A, B, C, A,B }、{ A, B, D}、{ B, C, D, B }等。子序列和子串是不同的概念,子串的元素是在原序列中连续的。
    给定两个序列 X 和 Y,当另一序列 Z 既是 X 的子序列又是 Y 的子序列时,称 Z 是序列 X 和 Y 的一个公共子序列。最长公共子序列是长度最长的子序列。

LCS

    最长公共子序列(Longest Common Subsequence, LCS)问题:给定两个序列 X 和 Y,找出 X 和 Y 的一个最长公共子序列
    用动态规划(DP)求 LCS,复杂度是 O(nm)(n,m是序列的长度)
    例如,序列 X={ a, b, c, f, b, c }、Y={ a, b, f, c, a, b },如下图所示:

    用 L[i][j] 表示子序列 Xi 和 Yj 的最长公共子序列的长度
    当 Xi = Yj 时,找出 Xi-1 和 Yj-1 的最长公共子序列,然后在其尾部加上 Xi 即可得到 X 和 Y 的最长公共子序列
    当 Xi != Yj 时,求解两个子问题:(1)求 Xi-1 和 Yj 的最长公共子序列;(2)求 Xi 和 Yj-1 的最长公共子序列。然后取其中的最大值

    下面举例说明前几个步骤:
    步骤 1:求 L[1][1]。有 X1 = Y1,得 L[1][1] = L[0][0] + 1 = 1,如下图:

    步骤 2:求 L[1][2]。有 X1 != Y2,得 L[1][2] = max{ L[1][1], L[0][2] } = 1,如下图:

    后续步骤:继续以上过程,最后得到的结果如下图,L[6][6] 就是答案

    如果要输出方案,可采用回溯法:
    由于表格中每一个数由上一个数或者左一个数或左斜上方一个数直接得到,因此可以从最后一个数开始回溯。每次调用至少向左或向上(或同时向左向上)移动一步,故最多调用(m+n)次就会遇到 i=0 或 j=0 的情况,此时开始返回。因此算法复杂度为 O(m+n)

代码如下(Java)`

public class Main {
    int[][] dp = new int[1005][1005];
    String str1, str2;

    private int LCS(){
        for(int i=0; i<1005; i++)    //每次重置数组元素的值
            Arrays.fill(dp[i],0);
        char[] ch1 = str1.toCharArray();
        char[] ch2 = str2.toCharArray();
        for(int i=1; i<=ch1.length; i++)
            for(int j=1; j<=ch2.length; j++){
                if(ch1[i-1] == ch2[j-1])
                    dp[i][j] = dp[i-1][j-1] + 1;
                else dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
            }
        return dp[ch1.length][ch2.length];
    }

    private String print(){
        char[] ch1 = str1.toCharArray();
        char[] ch2 = str2.toCharArray();
        int m = ch1.length;   //m 为 str1 的长度
        int n = ch2.length;   //n 为 str2 的长度
        char[] result = new char[dp[m][n]];
        int index = result.length-1;   //从最后一个数开始回溯
        while (index>=0){
            if(n>0 && dp[m][n]==dp[m][n-1])   //向左移动
                n--;
            else if(m>0 && dp[m][n]==dp[m-1][n])   //向上移动
                m--;
            else {   //向左斜上方移动
                result[index--] = ch1[m-1];   //该位置即为需要记录的点
                m--;
                n--;
            }
        }
        return String.valueOf(result);
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        Main m = new Main();
        while (!sc.hasNext("#")){
            m.str1 = sc.nextLine();
            m.str2 = sc.nextLine();
            System.out.println(m.LCS());   //DP 求最长公共子序列
            System.out.println(m.print());   //打印出此最长公共子序列
        }
        sc.close();
    }
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值