最长公共子序列的变种:带字符出现次数限制的最长公共子序列(进阶版:限制某些字符必须连续出现k次,且总出现次数有限制)
字数 588 2025-11-15 01:35:00
最长公共子序列的变种:带字符出现次数限制的最长公共子序列(进阶版:限制某些字符必须连续出现k次,且总出现次数有限制)
问题描述
给定两个字符串s和t,以及一个整数k。要求找到s和t的最长公共子序列,且满足:
- 子序列中某些特定字符(比如字符'x')必须连续出现至少k次
- 子序列中每个字符的总出现次数不能超过该字符在原始字符串中的出现次数
解题思路
步骤1:理解问题核心
这是一个带有多重限制的LCS变种问题。我们需要在传统LCS的基础上加入:
- 特定字符的连续出现次数限制
- 字符总出现次数限制
步骤2:定义状态
我们使用四维DP数组:
dp[i][j][cnt][flag] 表示考虑s的前i个字符和t的前j个字符时,当前特定字符已经连续出现了cnt次,flag表示当前是否正在特定字符的连续段中。
步骤3:状态转移方程
情况1:当前字符不是特定字符
# 不选择当前字符
dp[i][j][0][0] = max(dp[i][j][0][0], dp[i-1][j][cnt][flag], dp[i][j-1][cnt][flag])
# 选择当前字符(当s[i-1] == t[j-1]时)
if s[i-1] == t[j-1] and s[i-1] != 特定字符:
dp[i][j][0][0] = max(dp[i][j][0][0], dp[i-1][j-1][cnt][flag] + 1)
情况2:当前字符是特定字符
# 开始新的连续段
if s[i-1] == t[j-1] and s[i-1] == 特定字符:
dp[i][j][1][1] = max(dp[i][j][1][1], dp[i-1][j-1][cnt][0] + 1)
# 继续当前连续段
if s[i-1] == t[j-1] and s[i-1] == 特定字符 and flag == 1:
dp[i][j][cnt+1][1] = max(dp[i][j][cnt+1][1], dp[i-1][j-1][cnt][1] + 1)
步骤4:处理出现次数限制
在状态转移时,我们需要检查字符出现次数:
# 检查字符c的出现次数是否超过限制
def check_limit(char, current_count):
max_count = min(s.count(char), t.count(char))
return current_count <= max_count
步骤5:处理连续出现次数要求
在最终结果中,我们只考虑满足连续出现次数要求的解:
result = 0
for cnt in range(k, max_continuous+1):
for flag in [0, 1]:
result = max(result, dp[n][m][cnt][flag])
完整算法实现
def constrained_lcs(s, t, k, special_chars):
n, m = len(s), len(t)
# 计算每个字符的最大允许出现次数
char_limits = {}
for char in set(s + t):
char_limits[char] = min(s.count(char), t.count(char))
# 最大连续次数不会超过字符串长度
max_cont = min(n, m)
# dp[i][j][cnt][flag]
# cnt: 当前特殊字符连续出现次数
# flag: 0-不在特殊字符段, 1-在特殊字符段
dp = [[[[-float('inf')] * 2 for _ in range(max_cont+1)]
for _ in range(m+1)] for _ in range(n+1)]
# 初始化
dp[0][0][0][0] = 0
for i in range(n+1):
for j in range(m+1):
for cnt in range(max_cont+1):
for flag in [0, 1]:
if dp[i][j][cnt][flag] < 0:
continue
current_val = dp[i][j][cnt][flag]
# 情况1:不选择s[i]或t[j]中的字符
if i < n:
dp[i+1][j][cnt][flag] = max(dp[i+1][j][cnt][flag], current_val)
if j < m:
dp[i][j+1][cnt][flag] = max(dp[i][j+1][cnt][flag], current_val)
# 情况2:选择匹配的字符
if i < n and j < m and s[i] == t[j]:
char = s[i]
# 检查总出现次数限制
current_char_count = current_val # 简化处理
if current_char_count >= char_limits[char]:
continue
if char in special_chars:
# 特殊字符
if flag == 1:
# 继续当前连续段
if cnt + 1 <= max_cont:
dp[i+1][j+1][cnt+1][1] = max(
dp[i+1][j+1][cnt+1][1], current_val + 1)
else:
# 开始新的连续段
dp[i+1][j+1][1][1] = max(
dp[i+1][j+1][1][1], current_val + 1)
else:
# 非特殊字符,重置连续计数
dp[i+1][j+1][0][0] = max(
dp[i+1][j+1][0][0], current_val + 1)
# 寻找满足条件的最优解
result = 0
for cnt in range(max_cont+1):
for flag in [0, 1]:
# 检查连续出现次数要求
if flag == 1 and cnt >= k:
result = max(result, dp[n][m][cnt][flag])
elif flag == 0:
result = max(result, dp[n][m][cnt][flag])
return result
复杂度分析
- 时间复杂度:O(n × m × L × 2),其中L是最大可能的连续次数
- 空间复杂度:O(n × m × L × 2)
这个算法通过多维DP状态巧妙地处理了连续出现次数和总出现次数的限制,是传统LCS问题的一个有趣变种。