最长公共子序列的变种:带字符必须按顺序出现限制的最长公共子序列(进阶版:限制某些字符必须连续出现k次)
字数 1244 2025-11-21 00:42:00

最长公共子序列的变种:带字符必须按顺序出现限制的最长公共子序列(进阶版:限制某些字符必须连续出现k次)

题目描述

给定两个字符串s和t,以及一个字符集合C和一个整数k。要求找到s和t的最长公共子序列,且满足:

  1. 对于集合C中的每个字符,如果在子序列中出现,则必须连续出现至少k次
  2. 子序列中C集合字符的连续出现段必须完整(不能截断)

例如:
s = "abcde", t = "ace", C = {'c'}, k = 1
最长公共子序列为 "ace",其中'c'连续出现1次,满足条件。

解题思路

步骤1:理解问题本质

这是一个带限制条件的最长公共子序列(LCS)问题。我们需要在标准的LCS动态规划基础上,增加对特定字符连续出现次数的限制。

步骤2:状态定义

定义dp[i][j][x]表示:

  • 考虑s的前i个字符和t的前j个字符
  • 当前正在构建的C集合字符的连续出现长度为x
  • 的值表示当前最长公共子序列长度

但这样定义状态空间太大,更好的方法是:
定义dp[i][j]表示考虑s的前i个字符和t的前j个字符时的最长公共子序列长度
定义cnt[i][j]表示在达到dp[i][j]时,当前C集合字符的连续出现长度

步骤3:状态转移方程

对于每个位置(i, j):

  1. 如果s[i-1] == t[j-1](字符匹配):

    • 如果当前字符 ∈ C:
      • 如果前一个状态cnt[i-1][j-1] >= k-1 或者 前一个字符不在C中:
        dp[i][j] = dp[i-1][j-1] + 1
        cnt[i][j] = cnt[i-1][j-1] + 1
      • 否则:
        跳过这个匹配(因为不满足连续k次的限制)
    • 如果当前字符 ∉ C:
      dp[i][j] = dp[i-1][j-1] + 1
      cnt[i][j] = 0(重置计数器)
  2. 如果s[i-1] != t[j-1](字符不匹配):
    dp[i][j] = max(dp[i-1][j], dp[i][j-1])
    选择cnt值时,要选择与dp值对应的cnt值

步骤4:初始化

  • dp[0][j] = 0, cnt[0][j] = 0(空字符串)
  • dp[i][0] = 0, cnt[i][0] = 0(空字符串)

步骤5:算法实现

def longestCommonSubsequenceWithConstraint(s, t, C, k):
    m, n = len(s), len(t)
    dp = [[0] * (n + 1) for _ in range(m + 1)]
    cnt = [[0] * (n + 1) for _ in range(m + 1)]
    
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if s[i-1] == t[j-1]:
                if s[i-1] in C:
                    # 检查是否满足连续k次的限制
                    if cnt[i-1][j-1] >= k - 1 or (i-1 == 0 or j-1 == 0 or s[i-2] not in C):
                        dp[i][j] = dp[i-1][j-1] + 1
                        cnt[i][j] = cnt[i-1][j-1] + 1
                    else:
                        # 不满足限制,跳过这个匹配
                        dp[i][j] = max(dp[i-1][j], dp[i][j-1])
                        cnt[i][j] = cnt[i-1][j-1]  # 保持原计数
                else:
                    # 普通字符,正常处理
                    dp[i][j] = dp[i-1][j-1] + 1
                    cnt[i][j] = 0  # 重置计数器
            else:
                # 字符不匹配,选择较大的那个
                if dp[i-1][j] > dp[i][j-1]:
                    dp[i][j] = dp[i-1][j]
                    cnt[i][j] = cnt[i-1][j]
                else:
                    dp[i][j] = dp[i][j-1]
                    cnt[i][j] = cnt[i][j-1]
    
    return dp[m][n]

步骤6:复杂度分析

  • 时间复杂度:O(m × n),其中m和n分别是两个字符串的长度
  • 空间复杂度:O(m × n),用于存储dp和cnt数组

示例验证

例1:s = "abcde", t = "ace", C = {'c'}, k = 1
结果:3(子序列"ace")

例2:s = "aabcc", t = "aacc", C = {'c'}, k = 2
结果:4(子序列"aacc",其中'c'连续出现2次)

这个算法在标准LCS的基础上,通过额外的计数数组来跟踪特定字符的连续出现情况,确保满足题目中的限制条件。

最长公共子序列的变种:带字符必须按顺序出现限制的最长公共子序列(进阶版:限制某些字符必须连续出现k次) 题目描述 给定两个字符串s和t,以及一个字符集合C和一个整数k。要求找到s和t的最长公共子序列,且满足: 对于集合C中的每个字符,如果在子序列中出现,则必须连续出现至少k次 子序列中C集合字符的连续出现段必须完整(不能截断) 例如: s = "abcde", t = "ace", C = {'c'}, k = 1 最长公共子序列为 "ace",其中'c'连续出现1次,满足条件。 解题思路 步骤1:理解问题本质 这是一个带限制条件的最长公共子序列(LCS)问题。我们需要在标准的LCS动态规划基础上,增加对特定字符连续出现次数的限制。 步骤2:状态定义 定义dp[ i][ j][ x ]表示: 考虑s的前i个字符和t的前j个字符 当前正在构建的C集合字符的连续出现长度为x 的值表示当前最长公共子序列长度 但这样定义状态空间太大,更好的方法是: 定义dp[ i][ j ]表示考虑s的前i个字符和t的前j个字符时的最长公共子序列长度 定义cnt[ i][ j]表示在达到dp[ i][ j ]时,当前C集合字符的连续出现长度 步骤3:状态转移方程 对于每个位置(i, j): 如果s[ i-1] == t[ j-1 ](字符匹配): 如果当前字符 ∈ C: 如果前一个状态cnt[ i-1][ j-1 ] >= k-1 或者 前一个字符不在C中: dp[ i][ j] = dp[ i-1][ j-1 ] + 1 cnt[ i][ j] = cnt[ i-1][ j-1 ] + 1 否则: 跳过这个匹配(因为不满足连续k次的限制) 如果当前字符 ∉ C: dp[ i][ j] = dp[ i-1][ j-1 ] + 1 cnt[ i][ j ] = 0(重置计数器) 如果s[ i-1] != t[ j-1 ](字符不匹配): dp[ i][ j] = max(dp[ i-1][ j], dp[ i][ j-1 ]) 选择cnt值时,要选择与dp值对应的cnt值 步骤4:初始化 dp[ 0][ j] = 0, cnt[ 0][ j ] = 0(空字符串) dp[ i][ 0] = 0, cnt[ i][ 0 ] = 0(空字符串) 步骤5:算法实现 步骤6:复杂度分析 时间复杂度:O(m × n),其中m和n分别是两个字符串的长度 空间复杂度:O(m × n),用于存储dp和cnt数组 示例验证 例1:s = "abcde", t = "ace", C = {'c'}, k = 1 结果:3(子序列"ace") 例2:s = "aabcc", t = "aacc", C = {'c'}, k = 2 结果:4(子序列"aacc",其中'c'连续出现2次) 这个算法在标准LCS的基础上,通过额外的计数数组来跟踪特定字符的连续出现情况,确保满足题目中的限制条件。