最长公共子序列

题目介绍

力扣1143题:https://leetcode-cn.com/problems/longest-common-subsequence/
给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。

在这里插入图片描述

分析

这也是一道动态规划的经典题目,很多较难的字符串问题都可以用类似的解法解决。在生物学应用中,经常会用到比较两个DNA基因序列的问题。DNA由A、C、G、T四种碱基分子构成,所以可以看作字母A、C、G、T构成的一个字符串。衡量两个DNA相似度的一个方法,就是看两者有多少相同的碱基对,而且,这些碱基对必须以相同的顺序出现。抽象之后,这就变成了字符串最长公共子序列的问题。

我们发现,这个问题是有最优子结构的。我们假设得到了字符串str1和str2的一个最长子序列lcs。现在考虑去掉str1和str2各自的最后一个字符(记为c1和c2),那么就有两种情况:

  • c1和c2相同,那么这也一定是lcs的最后一个字符(记为t)。而且我们可以确定,去掉这个字符之后,lcs同样是str1和str2的最长子序列。
  • c1和c2不同,那么lcs的最后一个字符t,至少跟c1、c2中的一个不同。str1、str2中与t不相同的那个末尾字符删除掉,不会影响结果,lcs仍是str1、str2的最长子序列。

如果我们按照字符串长度来划分阶段,很明显不同阶段中的子序列,是会重复遍历的。所以,这是一个典型的具有最优子结构、而且子问题有重叠的问题,可以用动态规划来解决。

动态规划实现

(1)划分阶段,定义状态
分别考虑字符串str1和str2长度从0增长到str.length的过程。每一对str1和str2,我们都可以求出一个当前的最长子序列(lcs)。而字符串增长之后的lcs,会跟之前的结果相关。
为了将子问题的解保存下来,我们定义一个二维矩阵dp,保存str1和str2在不同长度下得到的最长子序列长度。

在这里插入图片描述
这里dp[i][j] 的含义是:对于 str1[1…i] 和 str2[1…j],它们的 lcs 长度是 dp[i][j]。这里str1[1…i]表示字符串str1截取前i个字符的子串,对应索引应该取0 ~ i-1。例如上图中,dp[2][4] 的含义就是:对于 “ac” 和 “babc”,它们的 lcs 长度是 2。我们最终想得到的答案应该是 dp[3][6]。

2)确定决策,写出状态转移方程
如果已经得到了上一阶段的最长子序列,那么接下来就是要扩充str1和str2的长度,然后比对新增字符。对于dp[i, j]的计算,当前末尾字符为str1[i-1]和str2[j-1],我们把这两个字符分别记作c1和c2。可能有以下两种情况:
c1和c2相同
那么没有它们时,lcs为dp[i-1, j-1],现在可以在后面追加一个字符了,所以

dp[i, j] = dp[i-1, j-1] + 1

c1和c2不同
那么只追加一个c1或者一个c2时,可能lcs会有变化,而两个都加上的时候,就不会再变化了。所以

dp[i, j] = max(dp[i-1, j], dp[i, j-1] )

在这里插入图片描述
(3)构造最优解
最终我们可以得到dp数组的右下角元素,就是全局的最优解。

代码如下:

public int longestCommonSubSequence(String text1, String text2){
        int l1 = text1.length();
        int l2 = text2.length();

        // 定义一个二维矩阵
        int[][] dp = new int[l1+1][l2+1];

        // 遍历所有状态,递推求解
        for (int i = 1; i <= l1; i++){
            for (int j = 1; j <= l2; 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[l1][l2];
    }

复杂度分析

  • 时间复杂度:O(n1*n2)。其中n1、n2分别为字符串str1、str2的长度。
  • 空间复杂度:O(n1*n2)。用到了二维数组来保存状态。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值