最长公共子序列的变种——带字符必须按顺序出现限制的最长公共子序列(进阶版:限制某些字符必须连续出现k次)
字数 1018 2025-11-22 16:09:33

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

让我为您详细讲解这个线性动态规划问题。

问题描述

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

  1. 对于集合C中的每个字符,如果它出现在子序列中,则必须连续出现至少k次
  2. 子序列中字符的相对顺序与在原字符串中相同

解题思路

这个问题是LCS问题的变种,增加了字符必须连续出现的约束条件。我们需要设计一个动态规划状态来跟踪字符的连续出现情况。

详细解题步骤

步骤1:定义状态

dp[i][j][c][cnt]表示考虑s的前i个字符和t的前j个字符,以字符c结尾且该字符已连续出现cnt次时的最长公共子序列长度。

由于状态维度较高,我们需要优化:

  • dp[i][j]表示考虑s的前i个字符和t的前j个字符时的最长公共子序列长度
  • last_char[i][j]记录当前子序列的最后一个字符
  • cont_count[i][j]记录最后一个字符的连续出现次数

步骤2:状态转移方程

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

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

    • 如果当前字符不在集合C中,或者虽然连续出现但次数未达k次限制:
      dp[i][j] = dp[i-1][j-1] + 1
      last_char[i][j] = s[i-1]
      cont_count[i][j] = cont_count[i-1][j-1] + 1
      
    • 如果当前字符在集合C中且连续出现已达k次:
      dp[i][j] = dp[i-1][j-1] + 1
      last_char[i][j] = s[i-1]
      cont_count[i][j] = cont_count[i-1][j-1] + 1
      
  2. 如果字符不匹配,考虑跳过s的当前字符或t的当前字符:

    dp[i][j] = max(dp[i-1][j], dp[i][j-1])
    // 相应地更新last_char和cont_count
    

步骤3:处理连续出现约束

关键点:当遇到集合C中的字符时,必须检查连续出现次数:

  • 如果连续出现次数小于k,不能单独选择该字符(必须与前面的相同字符一起选择)
  • 只有当连续出现次数达到k时,才能完整地选择这个字符序列

步骤4:初始化

dp[0][j] = 0 对于所有j
dp[i][0] = 0 对于所有i
last_char[0][j] = '#'
last_char[i][0] = '#'
cont_count[0][j] = 0
cont_count[i][0] = 0

步骤5:算法实现

def constrained_lcs(s, t, C, k):
    m, n = len(s), len(t)
    dp = [[0] * (n+1) for _ in range(m+1)]
    last_char = [[''] * (n+1) for _ in range(m+1)]
    cont_count = [[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]:
                char = s[i-1]
                # 检查是否可以扩展前一个序列
                if last_char[i-1][j-1] == char:
                    new_count = cont_count[i-1][j-1] + 1
                else:
                    new_count = 1
                
                # 检查约束条件
                if char in C and new_count < k:
                    # 不能单独选择,必须寻找其他选择
                    dp[i][j] = max(dp[i-1][j], dp[i][j-1])
                    if dp[i][j] == dp[i-1][j]:
                        last_char[i][j] = last_char[i-1][j]
                        cont_count[i][j] = cont_count[i-1][j]
                    else:
                        last_char[i][j] = last_char[i][j-1]
                        cont_count[i][j] = cont_count[i][j-1]
                else:
                    # 可以正常选择
                    dp[i][j] = dp[i-1][j-1] + 1
                    last_char[i][j] = char
                    cont_count[i][j] = new_count
            else:
                # 字符不匹配,选择较大的那个
                if dp[i-1][j] > dp[i][j-1]:
                    dp[i][j] = dp[i-1][j]
                    last_char[i][j] = last_char[i-1][j]
                    cont_count[i][j] = cont_count[i-1][j]
                else:
                    dp[i][j] = dp[i][j-1]
                    last_char[i][j] = last_char[i][j-1]
                    cont_count[i][j] = cont_count[i][j-1]
    
    return dp[m][n]

步骤6:复杂度分析

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

示例演示

假设:

  • s = "aabbbcc"
  • t = "aabbcc"
  • C = {'b'}(只有字符'b'有约束)
  • k = 2('b'必须连续出现至少2次)

计算过程:

  1. 匹配"aa":正常处理
  2. 遇到第一个'b':由于约束,不能单独选择
  3. 遇到第二个'b':连续出现2次,满足约束,可以完整选择
  4. 继续匹配"cc":正常处理

最终得到LCS:"aabbcc"(长度为6)

这个算法确保了在寻找最长公共子序列时,满足特定字符必须连续出现k次的约束条件。

最长公共子序列的变种——带字符必须按顺序出现限制的最长公共子序列(进阶版:限制某些字符必须连续出现k次) 让我为您详细讲解这个线性动态规划问题。 问题描述 给定两个字符串s和t,以及一个字符集合C和整数k,要求找到s和t的最长公共子序列,且满足: 对于集合C中的每个字符,如果它出现在子序列中,则必须连续出现至少k次 子序列中字符的相对顺序与在原字符串中相同 解题思路 这个问题是LCS问题的变种,增加了字符必须连续出现的约束条件。我们需要设计一个动态规划状态来跟踪字符的连续出现情况。 详细解题步骤 步骤1:定义状态 设 dp[i][j][c][cnt] 表示考虑s的前i个字符和t的前j个字符,以字符c结尾且该字符已连续出现cnt次时的最长公共子序列长度。 由于状态维度较高,我们需要优化: dp[i][j] 表示考虑s的前i个字符和t的前j个字符时的最长公共子序列长度 last_char[i][j] 记录当前子序列的最后一个字符 cont_count[i][j] 记录最后一个字符的连续出现次数 步骤2:状态转移方程 对于每个位置(i,j): 如果 s[i-1] == t[j-1] (字符匹配): 如果当前字符不在集合C中,或者虽然连续出现但次数未达k次限制: 如果当前字符在集合C中且连续出现已达k次: 如果字符不匹配,考虑跳过s的当前字符或t的当前字符: 步骤3:处理连续出现约束 关键点:当遇到集合C中的字符时,必须检查连续出现次数: 如果连续出现次数小于k,不能单独选择该字符(必须与前面的相同字符一起选择) 只有当连续出现次数达到k时,才能完整地选择这个字符序列 步骤4:初始化 步骤5:算法实现 步骤6:复杂度分析 时间复杂度:O(m×n),其中m和n分别是两个字符串的长度 空间复杂度:O(m×n),用于存储dp表、last_ char表和cont_ count表 示例演示 假设: s = "aabbbcc" t = "aabbcc" C = {'b'}(只有字符'b'有约束) k = 2('b'必须连续出现至少2次) 计算过程: 匹配"aa":正常处理 遇到第一个'b':由于约束,不能单独选择 遇到第二个'b':连续出现2次,满足约束,可以完整选择 继续匹配"cc":正常处理 最终得到LCS:"aabbcc"(长度为6) 这个算法确保了在寻找最长公共子序列时,满足特定字符必须连续出现k次的约束条件。