1143. 最长公共子序列
1.题目描述
给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。
若这两个字符串没有公共子序列,则返回 0。
题目链接:https://leetcode-cn.com/problems/longest-common-subsequence/
2.思路
采用动态规划的策略,与最大上升子序列长度的动态规划解法不同的是,采用了二维的dp 数组(表格) 而非一维dp数组。
状态定义:
为了表示方便,先举例:
text1 = "abcd"
text2 = "ace"
dp table
\ | “” | “a” | “b” | “c” | “d” |
---|---|---|---|---|---|
“” | 0 | 0 | 0 | 0 | 0 |
“a” | 0 | 1 | 1 | 1 | 1 |
“c” | 0 | 1 | 1 | 2 | 2 |
“e” | 0 | 1 | 1 | 2 | 2 |
横轴、纵轴分别表示两个字符串的各个字符, 其中从第一列(行)表示空串的情况。
d p [ i ] [ j ] dp[i][j] dp[i][j]表示text2的前i个字符子串和text1的前j个字符字串的LCS(longest common string)长度。边界状态:当其中一个为空串时,其LCS值必为0。
状态转移方程:
d
p
[
i
+
1
]
[
j
+
1
]
=
d
p
[
i
]
[
j
]
+
1
i
f
t
e
x
t
1
[
i
+
1
]
=
=
t
e
x
t
2
[
j
+
1
]
dp[i+1][j+1] =dp[i][j]+1 \ if \ text1[i+1]==text2[j+1]
dp[i+1][j+1]=dp[i][j]+1 if text1[i+1]==text2[j+1]
d
p
[
i
+
1
]
[
j
+
1
]
=
m
a
x
(
d
p
[
i
]
[
j
+
1
]
,
d
p
[
i
+
1
]
[
j
]
)
i
f
t
e
x
t
1
[
i
+
1
]
!
=
t
e
x
t
2
[
j
+
1
]
dp[i+1][j+1] =max(dp[i][j+1], dp[i+1][j]) if \ text1[i+1]\ != text2[j+1]
dp[i+1][j+1]=max(dp[i][j+1],dp[i+1][j])if text1[i+1] !=text2[j+1]
简单的理解就是:在已知两个字符串的LCS的前提下,同时往两个字符串后添加一个字符,如果该两字符相等,则说明新的字符串对的LCS长度为 原LCS长度值加1。如果给两个字符不相等,向上、向左相邻的LCS值中两者较大值。向左(或向上)相邻的LCS值 等价于 在已知两个字符串的LCS值前提下,向其中一个字符串添加一个字母,那么新的LCS值就有两种可能,要么不变,要么加1。
3.代码
int longestCommonSubsequence(string text1, string text2) {
if (text1.empty() || text2.empty())
return 0;
// 找出两个字符串长度
int length_t1 = text1.size();
int length_t2 = text2.size();
// 定义 dp table
vector<int> a(length_t2+1, 0);
vector<vector<int>> dp(length_t1+1, a);
// 填充 dp table
for(int i = 1; i < length_t1+1; i++)
for (int j = 1; j < length_t2 + 1; j++) {
// 状态转移方程
if (text2[j - 1] == text1[i - 1])
dp[i][j] = dp[i - 1][j - 1] + 1;
else {
dp[i][j] = max(dp[i][j - 1], dp[i - 1][j]);
}
}
// dp table最后一个位置即为LCS值。
int lcs = dp[length_t1][length_t2];
return lcs;
}