最长公共子序列的变种——带字符必须按顺序出现限制的最长公共子序列(进阶版:限制某些字符必须连续出现k次)
字数 1018 2025-11-22 16:09:33
最长公共子序列的变种——带字符必须按顺序出现限制的最长公共子序列(进阶版:限制某些字符必须连续出现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次限制:
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
- 如果当前字符不在集合C中,或者虽然连续出现但次数未达k次限制:
-
如果字符不匹配,考虑跳过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次)
计算过程:
- 匹配"aa":正常处理
- 遇到第一个'b':由于约束,不能单独选择
- 遇到第二个'b':连续出现2次,满足约束,可以完整选择
- 继续匹配"cc":正常处理
最终得到LCS:"aabbcc"(长度为6)
这个算法确保了在寻找最长公共子序列时,满足特定字符必须连续出现k次的约束条件。