LeetCode1143. 最长公共子序列 动态规划详解

1143. 最长公共子序列

题目来源

1143. 最长公共子序列

题目分析

给定两个字符串 text1text2,返回它们的最长公共子序列的长度。如果不存在公共子序列,则返回 0。公共子序列是在不改变字符相对顺序的情况下从两个字符串中选出的字符组成的新字符串。

题目难度

  • 难度:中等

题目标签

  • 标签:动态规划

题目限制

  • 1 <= text1.length, text2.length <= 1000
  • text1text2 仅由小写英文字符组成。

解题思路

这道题可以使用动态规划的思想来解决,具体思路如下:

  1. 问题定义:设 dp[i][j] 表示 text1[0..i-1]text2[0..j-1] 的最长公共子序列的长度,目标是求出 dp[text1.length][text2.length]

  2. 状态转移

    • 如果 text1[i-1] == text2[j-1],则 dp[i][j] = dp[i-1][j-1] + 1
    • 如果 text1[i-1] != text2[j-1],则 dp[i][j] = max(dp[i-1][j], dp[i][j-1])
  3. 初始化

    • 初始化 dp[0][j] = 0dp[i][0] = 0,表示空字符串与任意字符串的最长公共子序列长度为 0
  4. 最终结果dp[text1.length][text2.length] 即为所求的最长公共子序列的长度。

核心算法步骤

  1. 初始化

    • 创建一个大小为 (text1.length + 1) x (text2.length + 1) 的二维数组 dp,并将 dp[0][j]dp[i][0] 初始化为 0
  2. 状态转移

    • 遍历 text1text2 的所有字符,根据字符是否相等更新 dp 数组。
  3. 输出结果

    • 返回 dp[text1.length][text2.length],即 text1text2 的最长公共子序列的长度。

代码实现

以下是求解最长公共子序列问题的 Java 代码:

/**
 * 1143. 最长公共子序列
 * @param text1 字符串1
 * @param text2 字符串2
 * @return 最长公共子序列的长度
 */
public int longestCommonSubsequence(String text1, String text2) {
    int n = text1.length();
    int m = text2.length();
    char[] chars1 = text1.toCharArray();
    char[] chars2 = text2.toCharArray();
    int[][] dp = new int[n + 1][m + 1];

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (chars1[i] == chars2[j]) {
                dp[i + 1][j + 1] = dp[i][j] + 1;
            } else {
                dp[i + 1][j + 1] = Math.max(dp[i][j + 1], dp[i + 1][j]);
            }
        }
    }
    return dp[n][m];
}

代码解读

  • longestCommonSubsequence 方法
    • dp[i][j] 表示 text1[0..i-1]text2[0..j-1] 的最长公共子序列长度。
    • 如果 text1[i-1]text2[j-1] 相等,则 dp[i][j] 等于 dp[i-1][j-1] + 1,否则等于 dp[i-1][j]dp[i][j-1] 中的较大值。

性能分析

  • 时间复杂度O(n * m),其中 nm 分别为 text1text2 的长度。
  • 空间复杂度O(n * m),因为我们使用了二维数组来存储中间结果。

测试用例

你可以使用以下测试用例来验证代码的正确性:

String text1 = "abcde";
String text2 = "ace";
int result1 = longestCommonSubsequence(text1, text2);
System.out.println(result1);
// 输出: 3 ("ace" 是 "abcde" 和 "ace" 的最长公共子序列)

text1 = "abc";
text2 = "abc";
int result2 = longestCommonSubsequence(text1, text2);
System.out.println(result2);
// 输出: 3 ("abc" 是 "abc" 和 "abc" 的最长公共子序列)

text1 = "abc";
text2 = "def";
int result3 = longestCommonSubsequence(text1, text2);
System.out.println(result3);
// 输出: 0 (没有公共子序列)

扩展讨论

优化写法

  • 空间优化
    • 可以将 dp 数组压缩为一维,使用滚动数组来优化空间复杂度。
// 滚动数组
        int n = text1.length();
        int m = text2.length();
        int[] dp = new int[m + 1];
        dp[0] = 0;
        for (int i = 1; i <= n; i++) {
            for (int j = 1, pre = 0; j <= m; j++) {
                int tmp = dp[j];
                if (text1.charAt(i - 1) == text2.charAt(j - 1)) {
                    dp[j] = pre + 1;
                } else {
                    dp[j] = Math.max(dp[j], dp[j - 1]);
                }
                pre = tmp;
            }
        }
        return dp[m];

其他实现

  • 递归 + 记忆化
    • 也可以通过递归加记忆化搜索的方法来解决该问题,从 text1text2 的尾部开始,逐步求解最长公共子序列。

总结

通过动态规划的思想,我们能够高效地解决最长公共子序列问题。通过合理定义 dp 数组的含义,状态转移方程,以及对初始状态的处理,可以解决类似的子序列问题。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值