线性动态规划:最长公共子序列的变种——带字符出现次数限制的最长公共子序列(进阶版:限制某些字符必须连续出现k次,且总出现次数有限制)
字数 1349 2025-11-16 14:50:31
线性动态规划:最长公共子序列的变种——带字符出现次数限制的最长公共子序列(进阶版:限制某些字符必须连续出现k次,且总出现次数有限制)
我将为您详细讲解这个线性动态规划问题。这是一个在经典最长公共子序列(LCS)基础上增加了多个约束条件的变种问题。
问题描述
给定两个字符串s和t,以及以下限制条件:
- 某些特定字符在公共子序列中必须连续出现至少k次
- 每个字符在公共子序列中的总出现次数不能超过给定的限制
我们需要找到满足这些条件的最长公共子序列的长度。
示例:
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: 当前字符连续出现的次数
状态转移方程
-
不选择当前字符:
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:初始化状态数组
# 假设字符串长度分别为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)
优化技巧
- 状态压缩:对于不需要特殊限制的字符,可以省略其计数维度
- 滚动数组:由于状态转移只依赖于前一行和前一列,可以使用滚动数组优化空间
- 提前剪枝:当字符计数超过限制时立即跳过
总结
这个问题的核心在于如何在经典LCS的基础上,通过增加状态维度来跟踪各种约束条件。关键在于:
- 准确理解每个约束条件的含义
- 合理设计状态表示
- 正确处理状态转移时的各种边界情况
通过这种扩展的状态设计,我们能够解决带有复杂约束条件的最长公共子序列问题。