线性动态规划:最长公共子序列的变种——带字符必须按顺序出现限制的最长公共子序列(进阶版:限制某些字符必须连续出现k次)
题目描述
给定两个字符串s和t,以及一个字符集C和整数k,要求找到s和t的最长公共子序列,满足:
- 对于字符集C中的每个字符,如果在子序列中出现,则必须连续出现至少k次
- 子序列中字符的相对顺序与在s和t中相同
例如:
s = "aabcbc", t = "abcabc", C = {'b'}, k = 2
最长公共子序列为"abbc"(b连续出现2次)
解题思路
步骤1:理解问题本质
这是一个带限制条件的最长公共子序列(LCS)问题。我们需要在标准的LCS动态规划基础上,增加对特定字符连续出现次数的限制。
步骤2:定义状态
定义dp[i][j][c][cnt]表示:
- 考虑s的前i个字符和t的前j个字符
- 当前正在处理的字符是c(如果当前没有处理字符,则为空)
- 当前字符c已经连续出现了cnt次
由于状态空间较大,我们需要仔细设计状态表示。
步骤3:状态转移方程
对于每个状态dp[i][j][c][cnt],考虑三种情况:
-
不选s[i]和t[j]
dp[i][j][c][cnt] = max(dp[i][j][c][cnt], dp[i-1][j][c][cnt], dp[i][j-1][c][cnt]) -
s[i] == t[j]时选择该字符
-
如果当前没有处理字符(c为空):
dp[i][j][s[i]][1] = max(dp[i][j][s[i]][1], dp[i-1][j-1][空字符][0] + 1) -
如果当前字符与s[i]相同:
dp[i][j][c][cnt+1] = max(dp[i][j][c][cnt+1], dp[i-1][j-1][c][cnt] + 1) -
如果当前字符与s[i]不同,且cnt >= k(满足连续出现条件):
dp[i][j][s[i]][1] = max(dp[i][j][s[i]][1], dp[i-1][j-1][c][cnt] + 1)
-
-
边界条件
dp[0][0][空字符][0] = 0
步骤4:优化状态表示
由于直接使用四维DP可能空间太大,我们可以优化:
- 只记录当前正在处理的字符和计数
- 使用两个二维数组交替计算
步骤5:具体实现
def longestCommonSubsequenceWithConstraint(s, t, C, k):
m, n = len(s), len(t)
# dp[i][j] = (current_char, count, length)
# 使用字典来存储不同的状态
dp_prev = {('', 0): 0} # (当前字符, 连续次数): 长度
for i in range(m + 1):
dp_curr = {}
for j in range(n + 1):
if i == 0 and j == 0:
dp_curr[('', 0)] = 0
continue
# 不选s[i-1]或t[j-1]
for state in dp_prev:
dp_curr[state] = max(dp_curr.get(state, 0), dp_prev[state])
if i > 0 and j > 0 and s[i-1] == t[j-1]:
char = s[i-1]
for (curr_char, count), length in dp_prev.items():
# 情况1: 开始新的字符序列
if curr_char == '':
dp_curr[(char, 1)] = max(dp_curr.get((char, 1), 0), length + 1)
# 情况2: 延续当前字符序列
elif curr_char == char:
dp_curr[(char, count + 1)] = max(dp_curr.get((char, count + 1), 0), length + 1)
# 情况3: 切换字符(只有满足条件时才允许)
elif count >= k or curr_char not in C:
dp_curr[(char, 1)] = max(dp_curr.get((char, 1), 0), length + 1)
dp_prev = dp_curr
# 处理最终状态,确保所有字符序列都满足条件
result = 0
for (curr_char, count), length in dp_prev.items():
if curr_char == '' or count >= k or curr_char not in C:
result = max(result, length)
return result
步骤6:验证示例
对于s = "aabcbc", t = "abcabc", C = {'b'}, k = 2:
- 有效子序列:"abbc"(长度4),其中b连续出现2次
- 无效子序列:"abc"(长度3),其中b没有连续出现至少2次
步骤7:复杂度分析
- 时间复杂度:O(m × n × S),其中S是状态数量
- 空间复杂度:O(m × n × S)
这个解法通过动态规划状态的设计,巧妙地处理了字符必须连续出现的限制条件,是标准LCS问题的有趣变种。