最长公共子序列

335 阅读2分钟

问题描述

给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。
​
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
​
例如,"ace""abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。

Snipaste_2023-03-29_14-22-01.jpg

题解

定义状态(定义子问题)

假设字符串 text1text2 的长度分别为 mn
dp[i][j]:表示text1[0,i]字符和text2[0,j]字符之间的最长公共子序列;
i=0,text1为空,空字符串和任何字符串的最长公共子序列都为0dp[0][j]=0j=0,text2为空,空字符串和任何字符串的最长公共子序列都为0dp[i][0]=0

状态转移方程(描述子问题之间的联系,分类讨论)

下标从0开始
text1[i-1]:第i个字符;
text2[j-1]:第j个字符;
​
考虑子问题之间的关系,开始分类讨论
当 text1[i-1] == text2[j-1]时:
基于text1[0,i-1]字符和text2[0,j-1]字符的最长公共子序列(前一个最优子问题)考虑,再增加一个字符(公共字符)。
因此 dp[i][j] = dp[i - 1][j - 1] + 1;
​
当 text1[i-1] != text2[j-1]时,考虑两种情况:
前一个子问题已最优,基于前一个子问题,当前只需考虑增加一个字符后的各种情况;
text1[0,i-1]字符和text2[0,j]的最长公共子序列;
text1[0,i]字符和text2[0,j-1]的最长公共子序列;
因为要得到最长公共子序列,所以取最大的。
因此 dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
所以状态转移方程可以描述为:
dp[i][j]={dp[i1][j1]+1,text1[i1]=text2[j1]max(dp[i1][j],dp[i][j1]),text1[i1]text2[j1]d p[i][j]= \begin{cases}d p[i-1][j-1]+1, & \operatorname{text}_1[i-1]=\operatorname{text}_2[j-1] \\ \max (d p[i-1][j], d p[i][j-1]), & \operatorname{text}_1[i-1] \neq \operatorname{text}_2[j-1]\end{cases}
最终计算得到dp[m][n]即为text1和text2的最长公共子序列的长度。

代码

public static int solution(String text1, String text2) {
    int len1 = text1.length();
    int len2 = text2.length();
    // dp初始化为0
    int[][] dp = new int[len1 + 1][len2 + 1];
    for (int i = 1; i < dp.length; i++) {
        for (int j = 1; j < dp[i].length; j++) {
            // 三种情况
            if (text1.charAt(i - 1) == text2.charAt(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[len1][len2];
}