线性动态规划:最长公共子序列的变种——带字符出现次数限制的最长公共子序列(进阶版:限制某些字符必须连续出现k次,且总出现次数有限制)
字数 1349 2025-11-16 14:50:31

线性动态规划:最长公共子序列的变种——带字符出现次数限制的最长公共子序列(进阶版:限制某些字符必须连续出现k次,且总出现次数有限制)

我将为您详细讲解这个线性动态规划问题。这是一个在经典最长公共子序列(LCS)基础上增加了多个约束条件的变种问题。

问题描述

给定两个字符串s和t,以及以下限制条件:

  1. 某些特定字符在公共子序列中必须连续出现至少k次
  2. 每个字符在公共子序列中的总出现次数不能超过给定的限制

我们需要找到满足这些条件的最长公共子序列的长度。

示例:

s = "aabbcc", t = "abcabc"
限制:
- 字符'a'必须连续出现至少2次
- 字符'b'在子序列中最多出现1次
- 字符'c'无特殊限制

解题思路

这个问题需要在经典LCS的动态规划基础上,增加状态来跟踪各种约束条件的满足情况。

状态定义

我们定义dp[i][j][cnt_a][cnt_b][last_char][consecutive]:

  • i: 在字符串s中考虑前i个字符
  • j: 在字符串t中考虑前j个字符
  • cnt_a: 字符'a'在当前子序列中的出现次数
  • cnt_b: 字符'b'在当前子序列中的出现次数
  • last_char: 上一个选择的字符(0表示空,1表示'a',2表示'b',3表示'c')
  • consecutive: 当前字符连续出现的次数

状态转移方程

  1. 不选择当前字符
    dp[i][j][cnt_a][cnt_b][last_char][consecutive] = max(
    dp[i-1][j][cnt_a][cnt_b][last_char][consecutive],
    dp[i][j-1][cnt_a][cnt_b][last_char][consecutive]
    )

  2. 当s[i] == t[j]时,选择当前字符
    如果当前字符与上一个字符相同:

    • 新的连续次数 = consecutive + 1
    • 检查是否满足连续出现限制

    如果当前字符与上一个字符不同:

    • 新的连续次数 = 1
    • 检查上一个字符是否满足连续出现限制

    同时更新对应字符的计数,并检查是否超过总次数限制。

详细解题步骤

步骤1:初始化状态数组

# 假设字符串长度分别为n和m
# 字符计数上限为max_cnt
dp = 一个(n+1)×(m+1)×(max_cnt+1)×(max_cnt+1)×4×(k+1)的数组初始化为-
dp[0][0][0][0][0][0] = 0  # 空子序列

步骤2:处理边界情况

  • 当i=0或j=0时,表示有一个字符串为空,此时子序列长度只能为0
  • 但需要根据last_char和consecutive来检查是否满足连续出现限制

步骤3:状态转移实现

对于每个i从0到n,j从0到m,遍历所有可能的计数和状态组合:

for i in range(n+1):
    for j in range(m+1):
        for cnt_a in range(max_cnt+1):
            for cnt_b in range(max_cnt+1):
                for last in range(4):
                    for cons in range(k+1):
                        # 跳过无效状态
                        if dp[i][j][cnt_a][cnt_b][last][cons] == -:
                            continue
                        
                        current = dp[i][j][cnt_a][cnt_b][last][cons]
                        
                        # 情况1:不选s[i](如果i<n)
                        if i < n:
                            # 状态转移...
                        
                        # 情况2:不选t[j](如果j<m)  
                        if j < m:
                            # 状态转移...
                        
                        # 情况3:选择s[i]和t[j](如果它们相等且i<n且j<m)
                        if i < n and j < m and s[i] == t[j]:
                            char = s[i]
                            new_cnt_a, new_cnt_b = cnt_a, cnt_b
                            
                            # 更新字符计数
                            if char == 'a':
                                if new_cnt_a >= max_cnt: continue
                                new_cnt_a += 1
                            elif char == 'b':  
                                if new_cnt_b >= max_cnt: continue
                                new_cnt_b += 1
                            
                            # 处理连续出现
                            if last == char_to_int(char):
                                new_cons = cons + 1
                                # 检查是否超过最大连续限制
                                if new_cons > k: continue
                            else:
                                # 字符变化,检查上一个字符是否满足最小连续要求
                                if last != 0 and last == 1 and cons < required_consecutive['a']:
                                    continue
                                new_cons = 1
                            
                            new_last = char_to_int(char)
                            dp[i+1][j+1][new_cnt_a][new_cnt_b][new_last][new_cons] = max(
                                dp[i+1][j+1][new_cnt_a][new_cnt_b][new_last][new_cons],
                                current + 1
                            )

步骤4:结果提取

最终结果是所有有效状态中的最大值,特别要注意:

  • 检查最后一个字符是否满足最小连续出现要求
  • 检查所有字符计数是否在限制范围内

复杂度分析

  • 时间复杂度:O(n×m×C²×4×k),其中C是最大字符计数
  • 空间复杂度:O(n×m×C²×4×k)

优化技巧

  1. 状态压缩:对于不需要特殊限制的字符,可以省略其计数维度
  2. 滚动数组:由于状态转移只依赖于前一行和前一列,可以使用滚动数组优化空间
  3. 提前剪枝:当字符计数超过限制时立即跳过

总结

这个问题的核心在于如何在经典LCS的基础上,通过增加状态维度来跟踪各种约束条件。关键在于:

  • 准确理解每个约束条件的含义
  • 合理设计状态表示
  • 正确处理状态转移时的各种边界情况

通过这种扩展的状态设计,我们能够解决带有复杂约束条件的最长公共子序列问题。

线性动态规划:最长公共子序列的变种——带字符出现次数限制的最长公共子序列(进阶版:限制某些字符必须连续出现k次,且总出现次数有限制) 我将为您详细讲解这个线性动态规划问题。这是一个在经典最长公共子序列(LCS)基础上增加了多个约束条件的变种问题。 问题描述 给定两个字符串s和t,以及以下限制条件: 某些特定字符在公共子序列中必须连续出现至少k次 每个字符在公共子序列中的总出现次数不能超过给定的限制 我们需要找到满足这些条件的最长公共子序列的长度。 示例: 解题思路 这个问题需要在经典LCS的动态规划基础上,增加状态来跟踪各种约束条件的满足情况。 状态定义 我们定义dp[ i][ j][ cnt_ a][ cnt_ b][ last_ char][ consecutive ]: i: 在字符串s中考虑前i个字符 j: 在字符串t中考虑前j个字符 cnt_ a: 字符'a'在当前子序列中的出现次数 cnt_ b: 字符'b'在当前子序列中的出现次数 last_ char: 上一个选择的字符(0表示空,1表示'a',2表示'b',3表示'c') consecutive: 当前字符连续出现的次数 状态转移方程 不选择当前字符 : dp[ i][ j][ cnt_ a][ cnt_ b][ last_ char][ consecutive ] = max( dp[ i-1][ j][ cnt_ a][ cnt_ b][ last_ char][ consecutive ], dp[ i][ j-1][ cnt_ a][ cnt_ b][ last_ char][ consecutive ] ) 当s[ i] == t[ j]时,选择当前字符 : 如果当前字符与上一个字符相同: 新的连续次数 = consecutive + 1 检查是否满足连续出现限制 如果当前字符与上一个字符不同: 新的连续次数 = 1 检查上一个字符是否满足连续出现限制 同时更新对应字符的计数,并检查是否超过总次数限制。 详细解题步骤 步骤1:初始化状态数组 步骤2:处理边界情况 当i=0或j=0时,表示有一个字符串为空,此时子序列长度只能为0 但需要根据last_ char和consecutive来检查是否满足连续出现限制 步骤3:状态转移实现 对于每个i从0到n,j从0到m,遍历所有可能的计数和状态组合: 步骤4:结果提取 最终结果是所有有效状态中的最大值,特别要注意: 检查最后一个字符是否满足最小连续出现要求 检查所有字符计数是否在限制范围内 复杂度分析 时间复杂度:O(n×m×C²×4×k),其中C是最大字符计数 空间复杂度:O(n×m×C²×4×k) 优化技巧 状态压缩 :对于不需要特殊限制的字符,可以省略其计数维度 滚动数组 :由于状态转移只依赖于前一行和前一列,可以使用滚动数组优化空间 提前剪枝 :当字符计数超过限制时立即跳过 总结 这个问题的核心在于如何在经典LCS的基础上,通过增加状态维度来跟踪各种约束条件。关键在于: 准确理解每个约束条件的含义 合理设计状态表示 正确处理状态转移时的各种边界情况 通过这种扩展的状态设计,我们能够解决带有复杂约束条件的最长公共子序列问题。