动态规划——多样本位置全对应的尝试模型,两个字符串的最长公共子序列问题

题目

给定两个字符串str1和str2,str1="a123bc",str2="12dea3f"。那么这两个字符串的最长公共子序列的长度是多少?

很明显,根据这个题目的类型再加上我们以往的经验,我们可以根据题目给出的两个样本做出一个二维表:

int[][] dp=new int[str1.length][str2.length];

动态规划——多样本位置全对应的尝试模型,两个字符串的最长公共子序列问题
那么,这个表的含义是什么呢?

表中任意一个格子(dpi),就代表str1[0]到str1[i]和str2[0]到str2[j]的最长公共子序列的长度是多少。因为我们要求的问题是两个字符串的整体的最长公共子序列有多长。

那么把右下角的格子算出来就是我们要求的主问题。

动态规划——多样本位置全对应的尝试模型,两个字符串的最长公共子序列问题
记住这一点:dpi代表str1[0]到str1[i]和str2[0]到str2[j]的最长公共子序列的长度!!!

1)填dp0的值,两个字符串都只拿第一个字符比较,相同就说明最长公共子序列长度为1;否则为0

dp[0][0]=str1[0]==str2[0]?1:0;

2)填第0行数据:str1只拿第一个字符,和str2整体比较

for(int j=1; j<str2.length; j++) {
    dp[0][j]=Math.max(dp[0][j-1], str2[j]==str1[0]?1:0);
}

3)填第0列数据:str2只拿第一个字符,和str1整体比较

for(int i=1; i<str1.length; i++) {
    dp[i][0]=Math.max(dp[i-1][0],str1[i]==str2[0]?1:0);
}

动态规划——多样本位置全对应的尝试模型,两个字符串的最长公共子序列问题
接下来,任何普遍位置如何尝试呢?有4种可能性:

1)两个字符串的最长公共子序列既不以str1[i]结尾,也不以str2[j]结尾,比如"a123b"和"c123e"的最长公共子序列是"123"。也就是说,str1[0]到str1[i]和str2[0]到str2[j]的最长公共子序列的长度等同于str1[0]到str1[i-1]和str2[0]到str2[j-1]的最长公共子序列的长度。也即是说,任何一个普遍位置的格子有可能依赖其左上角的格子!!!
动态规划——多样本位置全对应的尝试模型,两个字符串的最长公共子序列问题

2)两个字符串的最长公共子序列以str1[i]结尾,但是不以str2[j]结尾,比如"ab123"和"c123ef"的最长公共子序列是"123"。也就是说,str1[0]到str1[i]和str2[0]到str2[j]的最长公共子序列的长度等同于str1[0]到str1[i]和str2[0]到str2[j-1]的最长公共子序列的长度。也即是说,任何一个普遍位置的格子有可能依赖其左边的格子!!!
动态规划——多样本位置全对应的尝试模型,两个字符串的最长公共子序列问题
3)两个字符串的最长公共子序列不以str1[i]结尾,但是以str2[j]结尾,比如"a123bc"和"ef123"的最长公共子序列是"123"。也就是说,str1[0]到str1[i]和str2[0]到str2[j]的最长公共子序列的长度等同于str1[0]到str1[i-1]和str2[0]到str2[j]的最长公共子序列的长度。也即是说,任何一个普遍位置的格子有可能依赖其右上角的格子!!!
动态规划——多样本位置全对应的尝试模型,两个字符串的最长公共子序列问题
4)两个字符串的最长公共子序列既以str1[i]结尾,也以str2[j]结尾,比如"ab12c3"和"d12ef3"的最长公共子序列是"123"。但是前提条件必须是str1[i]==str2[j]。也就是说,str1[0]到str1[i]和str2[0]到str2[j]的最长公共子序列的长度等同于str1[0]到str1[i-1]和str2[0]到str2[j-1]的最长公共子序列的长度再加1!!!

除此以外,再无其它可能性。这种可能性组织方式的关键是最长公共子序列最后一个字符在哪个位置。

完整代码:

package com.harrison.class13;

public class Code08_LongestCommonSubsequence {
    public static int lcs(char[] str1,char[] str2) {
        int[][] dp=new int[str1.length][str2.length];
        dp[0][0]=str1[0]==str2[0]?1:0;
        // 填第0列的值
        for(int i=1; i<str1.length; i++) {
            dp[i][0]=Math.max(dp[i-1][0],str1[i]==str2[0]?1:0);
        }
        // 填第0行的值
        for(int j=1; j<str2.length; j++) {
            dp[0][j]=Math.max(dp[0][j-1], str2[j]==str1[0]?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没中,为什么可能性1(dp[i-1][j-1])可以省略
                // 因为可能性2和3必然会覆盖可能性1
                if(str1[i]==str2[j]) {
                    dp[i][j]=Math.max(dp[i][j], dp[i-1][j-1]+1);
                }
            }
        }
        return dp[str1.length-1][str2.length-1];
    }
    
    public static void main(String[] args) {
        System.out.println(lcs("a123bc".toCharArray(),"12dea3f".toCharArray()));
    }
}
上一篇:WCF服务在JavaScript中使用ASP.NET的AJAX方法


下一篇:动态规划——背包问题