最长公共子序列的变种:带字符必须按顺序出现限制的最长公共子序列
字数 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),我们有三种选择:

  1. 字符匹配且与p匹配
  2. 字符匹配但不与p匹配
  3. 字符不匹配

详细解题步骤

步骤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:边界情况处理

  1. 空字符串: 如果p为空,退化为经典LCS问题
  2. 无解情况: 如果最终dp[m][n][l] < l,说明无法找到包含完整p的公共子序列
  3. 模式串比LCS长: 直接返回0

这个算法通过三维DP表巧妙地跟踪了模式串的匹配进度,确保最终结果既是最长公共子序列,又包含指定的模式串。

最长公共子序列的变种:带字符必须按顺序出现限制的最长公共子序列 让我为您详细讲解这个线性动态规划问题。 问题描述 给定两个字符串s和t,以及一个模式串p,我们需要找到s和t的最长公共子序列,且这个公共子序列必须包含模式串p作为子序列(即p中的字符必须按顺序出现在结果中,但不要求连续)。 示例: 解题思路 这个问题是经典最长公共子序列(LCS)问题的变种,我们需要在计算LCS的同时,确保结果包含特定的模式串。 状态定义 我们使用三维动态规划: dp[i][j][k] 表示考虑s的前i个字符、t的前j个字符,且已经匹配了p的前k个字符时的最长公共子序列长度。 状态转移方程 对于每个位置(i, j, k),我们有三种选择: 字符匹配且与p匹配 字符匹配但不与p匹配 字符不匹配 详细解题步骤 步骤1:初始化DP表 步骤2:填充基础情况 当k=0时,表示我们还没有开始匹配模式串p: 如果i=0或j=0,LCS长度为0 否则按经典LCS方式计算 步骤3:状态转移 步骤4:完整算法实现 步骤5:示例演示 让我们用具体例子来理解: 输入: DP表填充过程: 初始化: 逐步填充: 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表巧妙地跟踪了模式串的匹配进度,确保最终结果既是最长公共子序列,又包含指定的模式串。