• 最长公共子序列(力扣第1143题)


    1143.最长公共子序列

    ​ 给定两个字符串text1和text2,返回这两个字符串的最长公共子序列的长度。

    ​ 一个字符串的子序列是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
    ​ 例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。

    ​ 若这两个字符串没有公共子序列,则返回 0。

    示例 1:

    输入:text1 = "abcde", text2 = "ace" 
    输出:3  
    解释:最长公共子序列是 "ace",它的长度为 3。
    

    示例 2:

    输入:text1 = "abc", text2 = "abc"
    输出:3
    解释:最长公共子序列是 "abc",它的长度为 3。
    

    示例 3:

    输入:text1 = "abc", text2 = "def"
    输出:0
    解释:两个字符串没有公共子序列,返回 0。
    

    ​ 对于两个子序列 S1 和 S2,找出它们最长的公共子序列。

    ​ 定义一个二维数组dp用来存储最长公共子序列的长度,其中 dp[i][j]表示S1的前i个字符与S2的前j个字符最长公共子序列的长度。考虑 S1i 与 S2j 值是否相等,分为两种情况:

    • 当 S1i==S2j 时,那么就能在S1的前 i-1 个字符与 S2 的前 j-1 个字符最长公共子序列的基础上再加上 S1i 这个值,最长公共子序列长度加1,即 dp[i][j] = dp[i-1][j-1] + 1。
    • 当 S1i != S2j 时,此时最长公共子序列为 S1 的前 i-1 个字符和 S2 的前j个字符最长公共子序列,或者 S1 的前 i 个字符和 S2 的前 j-1 个字符最长公共子序列,取它们的最大者,即 dp[i][j] = max{ dp[i-1][j], dp[i][j-1] }。

    综上,最长公共子序列的状态转移方程为:

    img

    对于长度为 N 的序列 S1 和长度为 M 的序列 S2,dp[N][M] 就是序列 S1 和序列 S2 的最长公共子序列长度。

    与最长递增子序列相比,最长公共子序列有以下不同点:

    • 针对的是两个序列,求它们的最长公共子序列。
    • 在最长递增子序列中,dp[i] 表示以 Si 为结尾的最长递增子序列长度,子序列必须包含 Si ;在最长公共子序列中,dp[i][j] 表示 S1 中前 i 个字符与 S2 中前 j 个字符的最长公共子序列长度,不一定包含 S1i 和 S2j。
    • 在求最终解时,最长公共子序列中 dp[N][M] 就是最终解,而最长递增子序列中 dp[N] 不是最终解,因为以 SN 为结尾的最长递增子序列不一定是整个序列最长递增子序列,需要遍历一遍 dp 数组找到最大者。
    public static int longestCommonSubsequence(String text1, String text2) {
    
        int n = text1.length();
        int m = text2.length();
    
        int[][] dp = new int[n+1][m+1];
    
        for (int i = 1; i <= text1.toCharArray().length; i++) {
    
            for (int i1 = 1; i1 <= text2.toCharArray().length; i1++) {
    
                if (text1.charAt(i-1) == text2.charAt(i1-1)){
                    dp[i][i1] = dp[i-1][i1-1] + 1;
                }else {
    
                    dp[i][i1] = Math.max(dp[i][i1-1],dp[i-1][i1]);
                }
            }
        }
        return dp[n][m];
    }
    

    ​ 上面求的是两个字符串的最大公共子序列的长度,如果想要得到两个字符串的最大公共子序列是什么,那么可以拿根据状态转移数组dp来获取:

    ​ 易知,两个字符串的最大公共子序列的长度值位于状态转移数组的最右下方位置,即dp[n][m],那我们就从这个位置出发寻找最长公共子序列:

    ​ 1、从右下角出发,可以移动的方式一共有三种:向上,向左,向左上。设移动的过程中,i表示此时所在的行数、j表示此时所在的列数;

    ​ 2、如果dp[i][j] == dp[i-1][j] 或者dp[i][j] == dp[i][j-1] ,说明str1[i] != str2[j] ,那么直接向左或者向上移动即可;

    ​ 3、如果dp[i][j]大于dp[i-1][j]和dp[i][j-1],那么说明在计算dp[i][j]的时候,str1[i] == str2[j],所以选择了dp[i-1][j-1] + 1,此字符一定属于最长公共子序列,将此字符放入数组中,然后继续向左上方移动。

    public static int[][] longestCommonSubsequence2(String text1, String text2) {
    
        int n = text1.length();
        int m = text2.length();
    
        int[][] dp = new int[n+1][m+1];
    
        for (int i = 1; i <= text1.toCharArray().length; i++) {
    
            for (int i1 = 1; i1 <= text2.toCharArray().length; i1++) {
    
                if (text1.charAt(i-1) == text2.charAt(i1-1)){
                    dp[i][i1] = dp[i-1][i1-1] + 1;
                }else {
    
                    dp[i][i1] = Math.max(dp[i][i1-1],dp[i-1][i1]);
                }
            }
        }
    
        return dp;
    }
    
    public static String lcse(String text1, String text2){
        char[] str1 = text1.toCharArray();
        int n = text1.length();
        int m = text2.length();
        int[][] dp = longestCommonSubsequence2(text1,text2);
        char[] res = new char[dp[n][m]];
        int index = res.length - 1;
    
        while (index >= 0){
    
            if ( m > 0 && n > 0 && dp[n][m] > dp[n-1][m] && dp[n][m] > dp[n][m-1]){
    
                res[index--] = str1[n-1];
                m--;
                n--;
            }else if (m > 0 && dp[n][m] == dp[n][m-1]){
                m--;
            }else if (n > 0 && dp[n][m] == dp[n-1][m]){
                n--;
            }
    
        }
        return String.valueOf(res);
    }
    

    ​ 其实寻找最长公共子序列的过程,就是回顾状态转移数组计算的过程,我们可以根据状态转移数组中的每一个元素的值与其左边和上方的元素值的大小关系确定,元素的行数和列数各自代表的在两个字符串中对应的字符是否相等,从而确定它们是否在最长公共子序列中。

  • 相关阅读:
    AI人脸识别SDK接入 — 参数优化篇(虹软)
    虹软人脸识别ArcFace2.0 Android SDK使用教程
    虹软免费人脸识别SDK注册指南
    python3+arcface2.0 离线人脸识别 demo
    Android利用RecyclerView实现列表倒计时效果
    将博客搬至CSDN
    Retrofit动态设置支持JSON和XML格式转换工厂
    Android 简单统计文本文件字符数、单词数、行数Demo
    [雨]个人项目设计分析
    Android:随机生成算数四则运算简单demo(随机生成2~4组数字,进行加减乘除运算)
  • 原文地址:https://www.cnblogs.com/yxym2016/p/14416060.html
Copyright © 2020-2023  润新知