最长公共子序列的变种:带限制的最长公共子序列
字数 1451 2025-10-28 08:36:45
最长公共子序列的变种:带限制的最长公共子序列
题目描述
给定三个字符串 s1、s2 和 s3,求 s1 和 s2 的最长公共子序列(LCS),且要求这个公共子序列中必须包含 s3 作为其子序列(即 s3 是 LCS 的一个连续或非连续子序列)。如果不存在满足条件的公共子序列,返回 0。
例如:
- 输入:
s1 = "abcde",s2 = "ace",s3 = "ae"
输出:3(LCS 为"ace",包含"ae") - 输入:
s1 = "abcde",s2 = "ace",s3 = "af"
输出:0(LCS"ace"不包含"af")
解题思路
本题是 LCS 的扩展,需在常规 LCS 的动态规划过程中增加对 s3 的约束。我们可以分两步处理:
- 先求
s1和s2的 LCS(经典 DP)。 - 检查 LCS 是否包含
s3,但直接提取 LCS 再判断会超时(LCS 可能指数级数量),因此需在 DP 过程中同时追踪与s3的匹配情况。
改进方法:
定义三维 DP 数组 dp[i][j][k]:
- 表示
s1的前i个字符、s2的前j个字符的公共子序列中,匹配到s3的前k个字符时的最长公共子序列长度。 - 若
k = len(s3),说明已完全包含s3。
状态转移:
- 若
s1[i-1] == s2[j-1]:- 字符可加入公共子序列,检查它是否与
s3[k]匹配:- 若匹配,则
k可增加 1:dp[i][j][k] = max(dp[i][j][k], dp[i-1][j-1][k-1] + 1) - 无论是否匹配,都可选择不匹配
s3:dp[i][j][k] = max(dp[i][j][k], dp[i-1][j-1][k] + 1)
- 若匹配,则
- 字符可加入公共子序列,检查它是否与
- 若字符不相等:
- 继承
s1或s2缩短一个字符的最大值:dp[i][j][k] = max(dp[i-1][j][k], dp[i][j-1][k])
- 继承
初始化:
dp[0][j][k] = dp[i][0][k] = -INF(无效值,因为无法从空串匹配非空s3)- 例外:
k = 0时,表示尚未开始匹配s3,此时长度为 0,即dp[i][j][0] = 0。
最终答案:
dp[len(s1)][len(s2)][len(s3)],若为负则返回 0。
详细步骤
-
定义 DP 数组
- 维度:
(len(s1)+1) x (len(s2)+1) x (len(s3)+1) - 初始化所有值为
-10**9(表示不可能状态)。 - 对于所有
i, j,设dp[i][j][0] = 0(允许公共子序列不匹配任何s3的字符)。
- 维度:
-
三重循环迭代
for i in range(1, len(s1)+1): for j in range(1, len(s2)+1): for k in range(0, len(s3)+1): # 情况1:s1[i-1] == s2[j-1] if s1[i-1] == s2[j-1]: # 选项1:当前字符用于匹配 s3 的第 k 个字符(需 k>0 且 s1[i-1]==s3[k-1]) if k > 0 and s1[i-1] == s3[k-1]: dp[i][j][k] = max(dp[i][j][k], dp[i-1][j-1][k-1] + 1) # 选项2:当前字符不用于匹配 s3(即保持 k 不变) dp[i][j][k] = max(dp[i][j][k], dp[i-1][j-1][k] + 1) # 情况2:字符不相等,继承左侧或上侧 dp[i][j][k] = max(dp[i][j][k], dp[i-1][j][k], dp[i][j-1][k]) -
结果提取
- 若
dp[n][m][len(s3)] < 0,说明无法包含完整s3,返回 0;否则返回该值。
- 若
示例验证
以 s1 = "abcde", s2 = "ace", s3 = "ae" 为例:
- 最终状态
dp[5][3][2]的计算路径:- 匹配
'a'(k=1)后,继续匹配'e'(k=2),得到长度 3。
- 匹配
- 输出 3,符合预期。
该方法确保了在 O(nml) 时间内求解,兼顾了 LCS 和子序列包含约束。