最长公共子序列
题目
给出两个字符串,找到最长公共子序列(LCS),返回LCS的长度。
样例
样例 1:
输入:
A = "ABCD"
B = "EDCA"
输出:
1
解释:
LCS是 ‘A’ 或 ‘D’ 或 ‘C’
样例 2:
输入:
A = "ABCD"
B = "EACB"
输出:
2
解释:
LCS 是 “AC”
问题分析
这道属于动态规划的章程,可以按照动态规划的解题思路进行求解
首先介绍一下动态规划的要素
- 最优子结构性质
- 即问题的最优解包含了子问题的最优解
- 重叠子问题性质
- 子问题的求解类型相同,即子问题的重叠性质,因此对每个问题只解一次,然后将其解保存在一个表格中,当再次需要解此子问题时,只是简单地用常数时间查看一下结果
算法的步骤
- 找出最优解的性质,并刻画其结构特征
- 递归地定义最优值
- 以自底向上的方式计算最优值
- 根据计算最优值时得到的信息构造最优解
基本思想
将带求解问题分解成若干子问题,先求解子问题,再结合这些子问题的解得到原问题的解
可以使用一个表来记录所有已解决的子问题的答案,来减少重复计算的次数
相比于暴力求解,动态规划显得非常的巧妙和灵活,也节省了时间和空间资源。
问题求解
用上面的解题思路来求解这道题
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[i−1][j−1]+1dp[i][j]=max(dp[i−1][j],dp[i][j−1])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];
}
}
以上便是本道题的分析过程啦,如果有写的不到位的地方,欢迎各位大佬批评指正哈