最长公共子序列

最长公共子序列

原题链接:(https://www.lintcode.com/problem/77/)

题目

给出两个字符串,找到最长公共子序列(LCS),返回LCS的长度。

样例

样例 1:

输入:

A = "ABCD"
B = "EDCA"

输出:

1

解释:

LCS是 ‘A’ 或 ‘D’ 或 ‘C’
样例 2:

输入:

A = "ABCD"
B = "EACB"

输出:

2

解释:

LCS 是 “AC”

问题分析

这道属于动态规划的章程,可以按照动态规划的解题思路进行求解

首先介绍一下动态规划的要素
  • 最优子结构性质
    • 即问题的最优解包含了子问题的最优解
  • 重叠子问题性质
    • 子问题的求解类型相同,即子问题的重叠性质,因此对每个问题只解一次,然后将其解保存在一个表格中,当再次需要解此子问题时,只是简单地用常数时间查看一下结果
算法的步骤
  1. 找出最优解的性质,并刻画其结构特征
  2. 递归地定义最优值
  3. 自底向上的方式计算最优值
  4. 根据计算最优值时得到的信息构造最优解
基本思想

将带求解问题分解成若干子问题,先求解子问题,再结合这些子问题的解得到原问题的解

可以使用一个表来记录所有已解决的子问题的答案,来减少重复计算的次数

相比于暴力求解,动态规划显得非常的巧妙和灵活,也节省了时间和空间资源。

问题求解

用上面的解题思路来求解这道题

1. 设计状态

首先需要明白题目的要求,题目给定两个任意的字符串,求两个字符串之间公共的子序列的长度。

意思就是在“abcdef”和“acfg”之间公共的部分是‘a’,’c’,’f’,所以长度为3。

因此可以设计一张表来记录每次比较的结果,建立一张二维表,即二维数组dp[len][len], len是两个字符串里最长的字符串的长度

dp[i][j]就可以表示为A字符串前i个字符和B字符串前j个字符之间的公共子序列

那么第一步设计状态就完成啦

2.写出状态转移方程

接下来我们可以分析一下怎么进行比较,仔细想一下可以分成三种情况

  • 当前字符相同,即A[i] = B[j],既然字符相同的话,就直接+1就行了,在A[i - 1],B[j - 1]的基础上进行+1操作
  • 当前字符不同,又可以分成两种情况去比较
    • 用A[i - 1] 和 B[j]比较
    • 用A[i] 和 B[j - 1]比较

那么转移方程就可以写出来了
{ d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + 1 A [ i ] = B [ j ] d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) A [ i ] ≠ B [ j ] \begin{cases} dp[i][j] = dp[i - 1][j - 1] + 1 & A[i] = B[j]\\ dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) & A[i] \ne B[j] \end{cases} {dp[i][j]=dp[i1][j1]+1dp[i][j]=max(dp[i1][j],dp[i][j1])A[i]=B[j]A[i]=B[j]

3.设定初始状态

初始状态就很好想啦,如果A字符串为空字符的话,那么公共子序列就一定为0,同理,B字符串为空也是如此

4.执行状态转移

接下来就通过一张二维表来展示一下状态转移的过程

首先初始状态

请添加图片描述

然后进行状态的转移

请添加图片描述

红色箭头表示:字符’c’和‘i’都与字符串“banana”没有相同的字符,所以根据公式可以得出都为0,由dp[i - 1][j] 和dp[i][j - 1]的状态转移而来

在当前位置处于第一个相同的字符,因此可以根据公式,去找dp[i - 1][j - 1],因为dp[i - 1][j - 1]为0,所以dp[i][j] = 1

5.返回最终的解

请添加图片描述

最终结果如图可以,按照方程进行每次的比较,最终返回dp[A.length][B.length]即可

附上代码
C++版
class Solution {
public:
    /**
     * @param A: A string
     * @param B: A string
     * @return: The length of longest common subsequence of A and B
     */
    int longestCommonSubsequence(string &A, string &B) {
        // write your code here
        int lena = A.size();
        int lenb = B.size();
        int len = max(lena, lenb);
        vector<int> temp(len + 1, 0);
        // 初始化状态
        vector<vector<int>> dp(len + 1, temp);
        for (int i = 1; i <= lena; i++) {
            for (int j = 1; j <= lenb; j++) {
                // 根据情况执行状态转移
                if (A[i - 1] == B[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                } else {
                    dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
                }
            }
        }
        // 返回最终
        return dp[lena][lenb];
    }
};
java
public class Solution {
    /**
     * @param A: A string
     * @param B: A string
     * @return: The length of longest common subsequence of A and B
     */
    public int longestCommonSubsequence(String A, String B) {
        // write your code here
        int lena = A.length();
        int lenb = B.length();
        int len = Math.max(lena, lenb);
        int[][] dp = new int[len + 1][len + 1];
        // 初始状态
        for (int i = 0; i <= len; i++) {
            dp[i][0] = 0;
            dp[0][i] = 0;
        }
        for (int i = 1; i <= lena; i++) {
            for (int j = 1; j <= lenb; j++) {
                // 根据当前字符比较,执行状态转移
                // 因为字符串是从下标为0开始的,所以需要-1
                if (A.charAt(i - 1) == B.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[lena][lenb];
    }
}

以上便是本道题的分析过程啦,如果有写的不到位的地方,欢迎各位大佬批评指正哈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值