BM65 最长公共子序列(二)
题目描述
给定两个字符串 str1 和 str2,输出两个字符串的最长公共子序列。
如果最长公共子序列为空,则返回:
text
"-1"
题目保证:
text
目前给出的数据,仅存在一个最长公共子序列
数据范围
text
0 ≤ |str1|, |str2| ≤ 2000
要求:
text
时间复杂度:O(n²)
空间复杂度:O(n²)
示例
示例 1
输入:
text
"1A2C3D4B56","B1D23A456A"
返回:
text
"123456"
示例 2
输入:
text
"abc","def"
返回:
text
"-1"
示例 3
输入:
text
"abc","abc"
返回:
text
"abc"
示例 4
输入:
text
"ab",""
返回:
text
"-1"
解题思路
本题是经典的 最长公共子序列 LCS 问题。
注意:
text
子序列:可以不连续,但相对顺序不能改变
子串:必须连续
例如:
text
str1 = "abcde"
str2 = "ace"
"ace" 是公共子序列,但不是公共子串。
1. 定义 dp 数组
python
dp[i][j]
表示:
text
str1 前 i 个字符
和
str2 前 j 个字符
的最长公共子序列长度
例如:
text
dp[3][2]
表示:
text
str1 前 3 个字符
和
str2 前 2 个字符
的最长公共子序列长度
2. 为什么 dp 要多一行一列?
创建:
python
dp = [[0] * (m + 1) for _ in range(n + 1)]
原因是要表示空字符串:
text
dp[0][j]:str1 是空串
dp[i][0]:str2 是空串
空串和任何字符串的最长公共子序列长度都是 0。
3. 状态转移
情况 1:当前字符相等
python
if str1[i - 1] == str2[j - 1]:
说明这个字符可以加入公共子序列。
所以:
python
dp[i][j] = dp[i - 1][j - 1] + 1
原因:
text
当前两个字符已经匹配成功
所以两个字符串都要往前退一位
情况 2:当前字符不相等
python
else:
说明这两个字符不能同时作为公共子序列的一部分。
只能选择:
text
不要 str1 当前字符
或者
不要 str2 当前字符
所以:
python
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
4. 为什么还要倒推?
前面的 dp 表只保存了:
text
最长公共子序列的长度
但题目要求返回:
text
具体的最长公共子序列字符串
所以需要从 dp[n][m] 开始倒推。
倒推规则:
text
如果 str1[i - 1] == str2[j - 1]
说明这个字符属于答案,加入 res,然后 i--、j--
如果不相等
看 dp[i - 1][j] 和 dp[i][j - 1] 谁更大
往更大的方向走
因为是从后往前找,所以最后需要反转:
python
res[::-1]
Python 代码
python
class Solution:
def LCS(self, s1: str, s2: str) -> str:
n: int = len(s1)
m: int = len(s2)
# dp[i][j] 表示:
# s1 前 i 个字符 和 s2 前 j 个字符 的最长公共子序列长度
dp = [[0] * (m + 1) for _ in range(n + 1)]
# 1. 填 dp 表
for i in range(1, n + 1):
for j in range(1, m + 1):
if s1[i - 1] == s2[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + 1
else:
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
# 没有公共子序列
if dp[n][m] == 0:
return "-1"
# 2. 从右下角倒推出具体字符串
res = []
i = n
j = m
while i > 0 and j > 0:
if s1[i - 1] == s2[j - 1]:
res.append(s1[i - 1])
i -= 1
j -= 1
else:
if dp[i - 1][j] >= dp[i][j - 1]:
i -= 1
else:
j -= 1
# 因为倒推得到的是倒序,所以要反转
return ''.join(res[::-1])
复杂度分析
设:
text
n = len(s1)
m = len(s2)
时间复杂度
text
O(n × m)
原因:
text
需要遍历整个 dp 二维表
空间复杂度
text
O(n × m)
原因:
text
需要保存 dp 二维数组
核心
python
if s1[i - 1] == s2[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + 1
else:
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
最终:
text
前面双重 for:求最长公共子序列长度
后面 while:倒推出最长公共子序列字符串