线性动态规划:最长公共子序列的变种——带字符出现次数限制的最长公共子序列
题目描述
给定两个字符串 s 和 t,以及一个整数 k。要求找到 s 和 t 的最长公共子序列(LCS),但该子序列中每个字符的出现次数不能超过 k 次。例如,若 k=2,则子序列中每个字符最多出现两次。你需要返回满足条件的最长子序列的长度。
示例
- 输入:
s = "aabac", t = "acaba", k = 2 - 输出:
4 - 解释:满足条件的 LCS 为
"aaba"('a'出现 3 次,但k=2限制下最多允许 2 次,因此实际有效子序列为"aaba"中前两个'a'和'b'、'c'的组合,需动态规划计算)。
解题过程
1. 问题分析
传统 LCS 问题使用动态规划定义 dp[i][j] 表示 s[0..i-1] 和 t[0..j-1] 的 LCS 长度。本题新增限制:子序列中每个字符的出现次数不超过 k。这意味着状态需额外记录每个字符已出现的次数。
2. 状态设计
由于字符集可能很大,但实际出现字符有限,可优化状态。定义 dp[i][j][c] 表示考虑 s 的前 i 个字符、t 的前 j 个字符时,以字符 c 结尾且满足次数限制的 LCS 长度。但三维状态可能过高,需优化:
- 改为
dp[i][j][count]?但count范围是0..k,仍可接受。 - 更高效:用
dp[i][j]表示长度,同时维护辅助数组cnt[i][j][char]记录在(i,j)状态下每个字符已出现次数。但更新复杂。
实际方案:
定义 dp[i][j][x] 为考虑 s 前 i 字符、t 前 j 字符时,当前子序列中字符 c(需遍历所有字符)的出现次数为 x(0 ≤ x ≤ k)时的最大长度。但需对每个字符单独处理?可合并:
最终状态:dp[i][j][c][x] 表示以字符 c 结尾、且 c 已出现 x 次时的 LCS 长度。但四维状态过高。
优化:注意到只需记录当前末尾字符和其出现次数,因此状态为 dp[i][j][last_char][cnt],但 last_char 为字符类型,需离散化。字符集大小记为 C,则状态数 O(n*m*C*k)。若 C 小(如字母表),可行。
3. 状态转移
设字符集为 Σ,预处理每个字符在 s 和 t 中的位置。
状态定义:dp[i][j][ch][x] 表示在 s[0..i-1] 和 t[0..j-1] 中,满足条件且以字符 ch 结尾、ch 出现次数为 x 的子序列最大长度。
转移方程:
- 不选当前字符:继承
dp[i-1][j][ch][x]或dp[i][j-1][ch][x]。 - 选当前字符(当
s[i-1] == t[j-1] == ch):- 若
x == 1,则新子序列长度为1,或从之前状态转移:dp[i][j][ch][1] = max(1, dp[i-1][j-1][any_ch][any_cnt] + 1),但需满足新增ch后次数不超过k。 - 若
x > 1,则需从dp[i-1][j-1][ch][x-1]转移:dp[i][j][ch][x] = dp[i-1][j-1][ch][x-1] + 1(如果x-1 ≥ 1)。
- 若
同时,需考虑不以 ch 结尾的状态:维护 maxLen[i][j] 表示 (i,j) 所有状态的最大值,用于转移新字符。
4. 初始化
dp[0][j][ch][x] = 0(空串)dp[i][0][ch][x] = 0maxLen[0][j] = 0,maxLen[i][0] = 0
5. 算法步骤
- 遍历
i从0到n,j从0到m。 - 对于每个字符
ch在字符集Σ:- 继承状态:
dp[i][j][ch][x] = max(dp[i-1][j][ch][x], dp[i][j-1][ch][x]) - 若
s[i-1] == t[j-1] == ch:- 对于
x从1到k:- 若
x == 1:dp[i][j][ch][1] = max(dp[i][j][ch][1], maxLen[i-1][j-1] + 1) - 若
x > 1:dp[i][j][ch][x] = max(dp[i][j][ch][x], dp[i-1][j-1][ch][x-1] + 1)
- 若
- 对于
- 继承状态:
- 更新
maxLen[i][j] = max{ dp[i][j][ch][x] }对所有ch,x。
6. 答案提取
结果为 maxLen[n][m]。
7. 复杂度优化
状态数 O(n*m*C*k),若 C 大则需优化。可只处理实际出现的字符,且用滚动数组降空间。
示例计算(简略)
对 s="aabac", t="acaba", k=2:
- 字符集
{a, b, c},k=2。 - 逐步填表后,
maxLen[5][5]为 4(如子序列"abac"或"aaba"在限制下调整)。
通过以上步骤,可准确求出带次数限制的 LCS 长度。