最长公共子序列的变种:带限制的最长公共子序列(限制子序列必须包含某个特定子串)
题目描述
给定两个字符串s1和s2,以及一个特定子串t。要求找出s1和s2的最长公共子序列(LCS),但这个公共子序列必须包含子串t作为其连续部分。如果不存在这样的公共子序列,则返回0。
例如:
s1 = "abcde", s2 = "ace", t = "c"
最长公共子序列是 "ace",它包含 "c",所以答案是3。
s1 = "abcde", s2 = "axcye", t = "cd"
最长公共子序列是 "ace",但它不包含 "cd"。包含 "cd" 的公共子序列不存在,所以答案是0。
解题过程
这个问题的关键在于在寻找最长公共子序列的同时,确保特定子串t被包含在内。我们可以将问题分解为几个部分,并利用动态规划来跟踪状态。
步骤1:定义状态
我们需要一个三维动态规划数组dp[i][j][k],其中:
- i: 表示考虑s1的前i个字符
- j: 表示考虑s2的前j个字符
- k: 表示在匹配t的前k个字符方面的进度(0 ≤ k ≤ len(t))
k的含义:
- k = 0: 还没有开始匹配t中的任何字符
- 1 ≤ k ≤ len(t): 已经成功匹配了t的前k个字符
- 特别地,当k = len(t)时,表示已经完整包含了子串t
步骤2:状态转移方程
对于每个位置(i, j, k),我们需要考虑三种情况:
-
不选择s1[i]和s2[j]:
dp[i][j][k] = max(dp[i][j][k], dp[i-1][j-1][k]) -
当s1[i] = s2[j]时:
-
如果k = 0(还没有开始匹配t):
如果s1[i] = t[0],我们可以开始匹配t:dp[i][j][1] = max(dp[i][j][1], dp[i-1][j-1][0] + 1)
否则,正常匹配:dp[i][j][0] = max(dp[i][j][0], dp[i-1][j-1][0] + 1) -
如果1 ≤ k < len(t)(正在匹配t中):
如果s1[i] = t[k],继续匹配t:dp[i][j][k+1] = max(dp[i][j][k+1], dp[i-1][j-1][k] + 1)
否则,匹配失败,回到k=0:dp[i][j][0] = max(dp[i][j][0], dp[i-1][j-1][k] + 1) -
如果k = len(t)(已经完整匹配t):
正常匹配:dp[i][j][len(t)] = max(dp[i][j][len(t)], dp[i-1][j-1][len(t)] + 1)
-
-
不匹配当前字符的情况:
dp[i][j][k] = max(dp[i][j][k], dp[i-1][j][k], dp[i][j-1][k])
步骤3:初始化
- dp[0][j][0] = 0(空s1与任何s2的LCS长度为0)
- dp[i][0][0] = 0(空s2与任何s1的LCS长度为0)
- 对于k > 0,dp[0][j][k]和dp[i][0][k]都初始化为负无穷(表示不可能状态)
步骤4:最终答案
最终答案是dp[len(s1)][len(s2)][len(t)],即完整包含子串t的最长公共子序列长度。如果这个值小于0,说明不存在这样的子序列,返回0。
步骤5:算法实现
def constrained_lcs(s1, s2, t):
m, n, p = len(s1), len(s2), len(t)
# 创建三维DP数组,初始化为负无穷
dp = [[[-10**9] * (p + 1) for _ in range(n + 1)] for _ in range(m + 1)]
# 初始化:空字符串的情况
for i in range(m + 1):
for j in range(n + 1):
dp[i][j][0] = 0
# 填充DP表
for i in range(1, m + 1):
for j in range(1, n + 1):
for k in range(p + 1):
# 情况1:不选择当前字符
dp[i][j][k] = max(dp[i][j][k], dp[i-1][j][k], dp[i][j-1][k])
# 情况2:选择当前字符(只有当s1[i-1] == s2[j-1]时)
if s1[i-1] == s2[j-1]:
if k == 0:
# 还没有开始匹配t
if p > 0 and s1[i-1] == t[0]:
# 开始匹配t
dp[i][j][1] = max(dp[i][j][1], dp[i-1][j-1][0] + 1)
else:
# 正常匹配
dp[i][j][0] = max(dp[i][j][0], dp[i-1][j-1][0] + 1)
elif k < p:
# 正在匹配t中
if s1[i-1] == t[k]:
# 继续匹配t
dp[i][j][k+1] = max(dp[i][j][k+1], dp[i-1][j-1][k] + 1)
else:
# 匹配失败,回到k=0
dp[i][j][0] = max(dp[i][j][0], dp[i-1][j-1][k] + 1)
else: # k == p
# 已经完整匹配t,正常匹配
dp[i][j][p] = max(dp[i][j][p], dp[i-1][j-1][p] + 1)
result = dp[m][n][p]
return result if result > 0 else 0
复杂度分析
- 时间复杂度:O(m × n × p),其中m、n、p分别是s1、s2、t的长度
- 空间复杂度:O(m × n × p),可以使用滚动数组优化到O(n × p)
这个算法通过在传统LCS的基础上增加一个维度来跟踪特定子串t的匹配进度,确保了最终找到的公共子序列一定包含t作为连续子串。