首页 > 代码库 > 一天一道算法题(4)---最长公共子序列

一天一道算法题(4)---最长公共子序列

  • 题目

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

  • 解析

 本题是非常经典的动态规划问题,先来介绍求解动态规划表的过程。如果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]的最长公共子序列的长度。

2.矩阵dp第一行即dp[0][0..N-1]与步骤一同理,如果str1[0]==str2[j],则令dp[0][j]=1

3.对于其它位置(i,j),dp[i][j]的值只可能来自以下三种情况:

  • 可能是dp[i-1][j]
  • 可能是dp[i][j-1]
  • 可能是dp[i-1][j-1],就是str1[i]==str2[j]的时候,dp[i][j]=dp[i-1][j-1]+1

这三个可能的值中,选最大的作为dp[i][j]的值,具体参看下面代码getdp方法。

  public int[][] getdp(char[] str1, char[] str2) {
        int[][] dp = new int[str1.length][str2.length];
        dp[0][0] = str1[0] == str2[0] ? 1 : 0;
        for (int i = 1; i < str1.length; i++) {
            dp[i][0] = Math.max(dp[i-1][0], str1[i] == str2[0] ? 1 : 0);  //最大也就是1了,因为只比较了str2的一个字符
        }
        for (int j = 1; j < str2.length; j++) {
            dp[0][j] = Math.max(dp[0][j-1], str1[0] == str2[j] ? 1 : 0); //同上
        }
        for (int i = 1; i < str1.length; i++) {
            for (int j = 1; j < str2.length; j++) {
                dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
                if (str1[i] == str2[j]) {
                    dp[i][j] = Math.max(dp[i][j], dp[i-1][j-1] + 1); //找到3中方式中最大的长度
                }
            }
        }
        return dp;
    }

 

 

dp矩阵中最右下角的值代表str1和str2的最长公共子序列的长度。通过整个dp矩阵可以得到最长公共子序列。具体如下:

1.从矩阵的右下角开始,有三种移动方式:向上,向左,向左上。假设移动过程中,i表示此时的行数,j表示此时的列数,同时用一个变量res来表示最长公共子序列。

2.如果dp[i][j]大于dp[i-1][j]和dp[i][j-1],说明在计算dp[i][j]的时候,一定是选择dp[i-1][j-1],可以确定str1[i]等于str2[j],并且这个字符一定属于最长公共子序列,把这个字符放进res,然后向左上方移动。

3.如果dp[i][j]等于dp[i-1][j],向上方移动

4.如果dp[i][j]等于dp[i][j-1],同上,向左方移动。

5.如果dp[i][j]同时等于dp[i-1][j]和dp[i][j-1],向上还是向下无所谓

通过dp求解最长公共子序列的过程:

  public String lcse(String str1, String str2) {
        if (str1 == null || str2 == null || str1.equals("") || str2.equals("")) {
            return "";
        }
        char[] chs1 = str1.toCharArray();
        char[] chs2 = str2.toCharArray();
        int[][] dp = getdp(chs1, chs2);
        int m = chs1.length - 1;
        int n = chs2.length - 1;
        char[] res = new char[dp[m][n]];
        int index = res.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 {
                res[index--] = chs1[m];
                m--;
                n--;
            }
        }
        return String.valueOf(res);
    }

 

这只是打印出一种子序列,如果把所有的子序列都打印出来怎么做,明天更新。

  • 参考资料

《程序员代码面试指南》  左程云

一天一道算法题(4)---最长公共子序列