线性动态规划:最长公共子序列的变种——带字符替换限制的最长公共子序列(进阶版:允许部分字符替换为通配符)
字数 1885 2025-11-04 20:47:27
线性动态规划:最长公共子序列的变种——带字符替换限制的最长公共子序列(进阶版:允许部分字符替换为通配符)
题目描述
给定两个字符串 s1 和 s2,以及一个字符替换限制规则:某些字符可以被替换为通配符(用 * 表示,可匹配任意字符),但替换操作有成本。具体来说,每个字符替换为通配符的成本为 1,而直接匹配或通配符匹配的成本为 0。目标是找到两个字符串的一个子序列,使得通过最多进行 k 次字符替换(即替换成本不超过 k)后,子序列能够完全匹配。你需要计算满足条件的最长子序列的长度。
例如:
s1 = "abcde",s2 = "ace",k = 1:最长子序列为"ace",无需替换,长度为 3。s1 = "abcde",s2 = "acf",k = 1:将s2的'f'替换为通配符后匹配s1的'e',子序列"ace"长度为 3。
解题过程
步骤 1:定义状态
这是一个带限制的 LCS 变种问题。我们使用动态规划,定义状态 dp[i][j][t] 表示考虑 s1 的前 i 个字符和 s2 的前 j 个字符时,使用恰好 t 次替换操作所能得到的最长公共子序列长度。其中:
i的范围是0到len(s1)(i=0表示空字符串)。j的范围是0到len(s2)。t的范围是0到k(替换次数上限)。
步骤 2:状态转移方程
状态转移基于当前字符的匹配情况:
- 不选当前字符:继承之前的状态。
dp[i][j][t] = max(dp[i][j][t], dp[i-1][j][t], dp[i][j-1][t])
- 选当前字符:当
s1[i-1] == s2[j-1]时,直接匹配,成本不变。dp[i][j][t] = max(dp[i][j][t], dp[i-1][j-1][t] + 1)
- 使用替换操作:当
s1[i-1] != s2[j-1]但t > 0时,可以将其中一个字符替换为通配符(成本为 1),实现匹配。dp[i][j][t] = max(dp[i][j][t], dp[i-1][j-1][t-1] + 1)
注意:通配符匹配的本质是忽略字符差异,因此替换操作允许不相等字符被视为匹配。
步骤 3:初始化
- 当
i=0或j=0时,任意t下公共子序列长度为 0:dp[0][j][t] = 0对所有j, tdp[i][0][t] = 0对所有i, t
- 其他状态初始化为负无穷(或 -1),表示不可达。
步骤 4:计算顺序
按 i 从 0 到 len(s1),j 从 0 到 len(s2),t 从 0 到 k 的顺序遍历。
步骤 5:答案提取
最终答案是所有 t(0 ≤ t ≤ k)中 dp[len(s1)][len(s2)][t] 的最大值。
举例说明
以 s1 = "abcde", s2 = "acf", k = 1 为例:
- 初始化:
dp[0][j][t] = 0,dp[i][0][t] = 0。 - 计算
i=1, j=1:s1[0]='a'匹配s2[0]='a',直接匹配,dp[1][1][0] = 1。 i=2, j=2:s1[1]='b'不匹配s2[1]='c',但t=1时可替换,dp[2][2][1] = dp[1][1][0] + 1 = 2。i=3, j=3:s1[2]='c'匹配s2[2]='f'?不匹配,但t=1时替换'f'为通配符匹配'c',dp[3][3][1] = dp[2][2][0] + 1?注意dp[2][2][0]未定义(因之前需替换),实际应继承dp[2][2][1] = 2,替换后t=1已用,但匹配成功,长度变为 3。
最终得到最长子序列长度为 3。
优化点
- 空间优化:由于
dp[i][j][t]只依赖于i-1和j-1,可滚动数组压缩为二维dp[j][t]。 - 剪枝:当
t > min(i, j)时无需计算,因为替换次数不可能超过字符数。
通过以上步骤,我们解决了带替换限制的 LCS 问题,核心是在传统 LCS 上增加替换维度的状态管理。