最长公共子序列的变种:带限制的最长公共子序列(限制子序列必须包含某个特定子串)
字数 1648 2025-10-31 12:28:54

最长公共子序列的变种:带限制的最长公共子序列(限制子序列必须包含某个特定子串)

题目描述

给定两个字符串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),我们需要考虑三种情况:

  1. 不选择s1[i]和s2[j]:
    dp[i][j][k] = max(dp[i][j][k], dp[i-1][j-1][k])

  2. 当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)

  3. 不匹配当前字符的情况:
    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作为连续子串。

最长公共子序列的变种:带限制的最长公共子序列(限制子序列必须包含某个特定子串) 题目描述 给定两个字符串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:算法实现 复杂度分析 时间复杂度:O(m × n × p),其中m、n、p分别是s1、s2、t的长度 空间复杂度:O(m × n × p),可以使用滚动数组优化到O(n × p) 这个算法通过在传统LCS的基础上增加一个维度来跟踪特定子串t的匹配进度,确保了最终找到的公共子序列一定包含t作为连续子串。