最长回文子序列的变种:带字符替换限制的最长回文子序列(进阶版:替换操作有不同代价,且允许部分字符替换为通配符,同时限制某些字符必须连续出现)
字数 803 2025-11-18 08:22:20
最长回文子序列的变种:带字符替换限制的最长回文子序列(进阶版:替换操作有不同代价,且允许部分字符替换为通配符,同时限制某些字符必须连续出现)
我将为您详细讲解这个线性动态规划问题。这个问题结合了最长回文子序列、字符替换代价和连续字符限制等多个复杂要素。
问题描述
给定一个字符串s,我们可以进行以下操作:
- 删除字符(无代价)
- 替换字符(有不同代价)
- 某些字符必须连续出现至少k次
目标是通过这些操作,找到最长的回文子序列,同时最小化总代价。
问题分析
这是一个三维DP问题,我们需要考虑:
- 子序列的回文性质
- 字符替换的代价
- 连续字符的出现限制
- 通配符的特殊处理
解题步骤
步骤1:定义DP状态
定义dp[i][j][c]表示子串s[i...j]在最后一个字符为c时的最小代价和最大长度。
由于问题复杂,我们使用一个结构体来存储状态:
class State:
def __init__(self):
self.length = 0 # 当前回文子序列长度
self.cost = float('inf') # 当前总代价
self.last_char = '' # 最后一个字符
self.continuous = 0 # 当前连续出现次数
步骤2:状态转移方程
对于区间[i, j],考虑三种情况:
情况1:不包含s[i]和s[j]
dp[i][j] = min_state(dp[i][j], dp[i+1][j])
dp[i][j] = min_state(dp[i][j], dp[i][j-1])
情况2:s[i] == s[j]
如果两端字符相同,可以直接作为回文对:
if s[i] == s[j]:
new_state = dp[i+1][j-1]
new_state.length += 2
new_state.last_char = s[i]
new_state.continuous += (1 if new_state.last_char == s[i] else 1)
dp[i][j] = min_state(dp[i][j], new_state)
情况3:s[i] != s[j]
考虑替换操作:
# 将s[i]替换为s[j]的代价
cost1 = replace_cost[s[i]][s[j]]
state1 = dp[i+1][j-1]
state1.cost += cost1
state1.length += 2
state1.last_char = s[j]
dp[i][j] = min_state(dp[i][j], state1)
# 将s[j]替换为s[i]的代价
cost2 = replace_cost[s[j]][s[i]]
state2 = dp[i+1][j-1]
state2.cost += cost2
state2.length += 2
state2.last_char = s[i]
dp[i][j] = min_state(dp[i][j], state2)
步骤3:处理连续字符限制
对于必须连续出现至少k次的字符,我们需要额外检查:
def check_continuous_constraint(state, char, k):
if char in must_continuous_chars:
if state.last_char == char:
if state.continuous + 1 < k:
# 不满足连续出现要求
return False
else:
# 字符变化,重置计数
state.continuous = 1
return True
步骤4:处理通配符
通配符可以匹配任意字符:
if s[i] == '*' or s[j] == '*':
# 通配符可以匹配任意字符
for target_char in alphabet:
if s[i] == '*':
cost_i = wildcard_cost # 通配符使用代价
else:
cost_i = 0
if s[j] == '*':
cost_j = wildcard_cost
else:
cost_j = 0
new_state = dp[i+1][j-1]
new_state.length += 2
new_state.cost += cost_i + cost_j
new_state.last_char = target_char
dp[i][j] = min_state(dp[i][j], new_state)
步骤5:基础情况
# 单个字符
for i in range(n):
state = State()
state.length = 1
state.cost = 0
state.last_char = s[i]
state.continuous = 1
dp[i][i] = state
# 空字符串
for i in range(n+1):
state = State()
state.length = 0
state.cost = 0
state.last_char = ''
state.continuous = 0
dp[i][i-1] = state # 空区间
步骤6:状态比较函数
由于状态有多个维度,我们需要定义合适的状态比较:
def min_state(state1, state2):
if state1.length > state2.length:
return state1
elif state1.length < state2.length:
return state2
else:
# 长度相同时选择代价小的
return state1 if state1.cost < state2.cost else state2
完整算法实现
def longest_palindromic_subsequence_with_constraints(s, replace_cost, must_continuous_chars, k, wildcard_cost=1):
n = len(s)
# 初始化DP表
dp = [[State() for _ in range(n)] for _ in range(n)]
# 基础情况
for i in range(n):
state = State()
state.length = 1
state.cost = 0
state.last_char = s[i]
state.continuous = 1
dp[i][i] = state
# 动态规划
for length in range(2, n + 1):
for i in range(n - length + 1):
j = i + length - 1
# 情况1:跳过s[i]或s[j]
dp[i][j] = min_state(dp[i][j], dp[i+1][j])
dp[i][j] = min_state(dp[i][j], dp[i][j-1])
dp[i][j] = min_state(dp[i][j], dp[i+1][j-1])
# 情况2:处理通配符
if s[i] == '*' or s[j] == '*':
for target_char in set(s): # 所有可能的目标字符
cost_total = 0
if s[i] == '*':
cost_total += wildcard_cost
if s[j] == '*':
cost_total += wildcard_cost
new_state = State()
if length == 2:
new_state.length = 2
new_state.cost = cost_total
new_state.last_char = target_char
new_state.continuous = 2
else:
prev_state = dp[i+1][j-1]
new_state.length = prev_state.length + 2
new_state.cost = prev_state.cost + cost_total
new_state.last_char = target_char
new_state.continuous = prev_state.continuous + 1 if prev_state.last_char == target_char else 1
if check_continuous_constraint(new_state, target_char, k):
dp[i][j] = min_state(dp[i][j], new_state)
# 情况3:字符相同
elif s[i] == s[j]:
prev_state = dp[i+1][j-1]
new_state = State()
new_state.length = prev_state.length + 2
new_state.cost = prev_state.cost
new_state.last_char = s[i]
new_state.continuous = prev_state.continuous + 1 if prev_state.last_char == s[i] else 1
if check_continuous_constraint(new_state, s[i], k):
dp[i][j] = min_state(dp[i][j], new_state)
# 情况4:字符不同,考虑替换
else:
# 将s[i]替换为s[j]
cost1 = replace_cost.get((s[i], s[j]), float('inf'))
if cost1 < float('inf'):
prev_state = dp[i+1][j-1]
new_state1 = State()
new_state1.length = prev_state.length + 2
new_state1.cost = prev_state.cost + cost1
new_state1.last_char = s[j]
new_state1.continuous = prev_state.continuous + 1 if prev_state.last_char == s[j] else 1
if check_continuous_constraint(new_state1, s[j], k):
dp[i][j] = min_state(dp[i][j], new_state1)
# 将s[j]替换为s[i]
cost2 = replace_cost.get((s[j], s[i]), float('inf'))
if cost2 < float('inf'):
prev_state = dp[i+1][j-1]
new_state2 = State()
new_state2.length = prev_state.length + 2
new_state2.cost = prev_state.cost + cost2
new_state2.last_char = s[i]
new_state2.continuous = prev_state.continuous + 1 if prev_state.last_char == s[i] else 1
if check_continuous_constraint(new_state2, s[i], k):
dp[i][j] = min_state(dp[i][j], new_state2)
return dp[0][n-1]
# 辅助函数
def check_continuous_constraint(state, char, k):
if char in must_continuous_chars:
return state.continuous >= k
return True
复杂度分析
- 时间复杂度:O(n² × |Σ|),其中n是字符串长度,|Σ|是字符集大小
- 空间复杂度:O(n²)
示例说明
假设字符串s = "abc*ba",替换代价:('a','b')=1,必须连续字符:{'b'},k=2
算法会找到最优解:"bb"(通过通配符匹配和替换操作),长度为2,满足所有约束条件。
这个算法通过综合考虑长度、代价和连续约束,找到了在给定限制下的最优回文子序列。