最长公共子序列的变种:带字符必须按顺序出现限制的最长公共子序列
字数 1021 2025-11-20 00:25:52
最长公共子序列的变种:带字符必须按顺序出现限制的最长公共子序列
让我为您详细讲解这个线性动态规划问题。
问题描述
给定两个字符串s和t,以及一个模式串p,我们需要找到s和t的最长公共子序列,且这个公共子序列必须包含模式串p作为子序列(即p中的字符必须按顺序出现在结果中,但不要求连续)。
示例:
s = "abcde", t = "ace", p = "ce"
最长公共子序列为 "ace",其中包含 "ce" 作为子序列
解题思路
这个问题是经典最长公共子序列(LCS)问题的变种,我们需要在计算LCS的同时,确保结果包含特定的模式串。
状态定义
我们使用三维动态规划:
dp[i][j][k]表示考虑s的前i个字符、t的前j个字符,且已经匹配了p的前k个字符时的最长公共子序列长度。
状态转移方程
对于每个位置(i, j, k),我们有三种选择:
- 字符匹配且与p匹配
- 字符匹配但不与p匹配
- 字符不匹配
详细解题步骤
步骤1:初始化DP表
def longestCommonSubsequenceWithPattern(s, t, p):
m, n, l = len(s), len(t), len(p)
# 创建三维DP表,初始化为0
dp = [[[0] * (l + 1) for _ in range(n + 1)] for _ in range(m + 1)]
步骤2:填充基础情况
当k=0时,表示我们还没有开始匹配模式串p:
- 如果i=0或j=0,LCS长度为0
- 否则按经典LCS方式计算
步骤3:状态转移
for i in range(1, m + 1):
for j in range(1, n + 1):
for k in range(l + 1):
if s[i-1] == t[j-1]:
# 字符匹配的情况
if k > 0 and s[i-1] == p[k-1]:
# 当前字符与p的第k个字符匹配
dp[i][j][k] = max(dp[i][j][k], dp[i-1][j-1][k-1] + 1)
# 无论是否与p匹配,都可以选择包含这个字符
dp[i][j][k] = max(dp[i][j][k], dp[i-1][j-1][k] + 1)
else:
# 字符不匹配,继承之前的最佳结果
dp[i][j][k] = max(dp[i-1][j][k], dp[i][j-1][k])
步骤4:完整算法实现
def longestCommonSubsequenceWithPattern(s, t, p):
m, n, l = len(s), len(t), len(p)
dp = [[[0] * (l + 1) for _ in range(n + 1)] for _ in range(m + 1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
for k in range(l + 1):
# 不包含s[i-1]或t[j-1]的情况
dp[i][j][k] = max(dp[i-1][j][k], dp[i][j-1][k])
if s[i-1] == t[j-1]:
if k > 0 and s[i-1] == p[k-1]:
# 匹配p中的字符
dp[i][j][k] = max(dp[i][j][k], dp[i-1][j-1][k-1] + 1)
# 不匹配p中的字符,但仍然是公共字符
dp[i][j][k] = max(dp[i][j][k], dp[i-1][j-1][k] + 1)
# 检查是否完全匹配了模式串p
if dp[m][n][l] < l: # 如果最终结果长度小于p的长度,说明没有包含完整的p
return 0
return dp[m][n][l]
步骤5:示例演示
让我们用具体例子来理解:
输入:
s = "abcde", t = "ace", p = "ce"
DP表填充过程:
初始化:
dp[0][*][*] = 0
dp[*][0][*] = 0
逐步填充:
- i=1,j=1: s[0]='a', t[0]='a',匹配但不匹配p,k=0: dp[1][1][0]=1
- i=2,j=2: s[1]='b', t[1]='c',不匹配
- i=3,j=2: s[2]='c', t[1]='c',匹配且匹配p[0]='c',k=1: dp[3][2][1]=2
- i=4,j=3: s[3]='d', t[2]='e',不匹配
- i=5,j=3: s[4]='e', t[2]='e',匹配且匹配p[1]='e',k=2: dp[5][3][2]=3
最终结果: 3("ace")
步骤6:复杂度分析
- 时间复杂度: O(m × n × l),其中m、n、l分别是字符串s、t、p的长度
- 空间复杂度: O(m × n × l),可以使用滚动数组优化到O(n × l)
步骤7:边界情况处理
- 空字符串: 如果p为空,退化为经典LCS问题
- 无解情况: 如果最终dp[m][n][l] < l,说明无法找到包含完整p的公共子序列
- 模式串比LCS长: 直接返回0
这个算法通过三维DP表巧妙地跟踪了模式串的匹配进度,确保最终结果既是最长公共子序列,又包含指定的模式串。