线性动态规划:最长公共子序列的变种——带字符必须按顺序出现限制的最长公共子序列(进阶版:限制某些字符必须连续出现k次,且总出现次数有限制)
字数 2306 2025-12-02 03:58:41

线性动态规划:最长公共子序列的变种——带字符必须按顺序出现限制的最长公共子序列(进阶版:限制某些字符必须连续出现k次,且总出现次数有限制)

题目描述
给定两个字符串 st,以及一个字符集合 C(例如 C = {'a', 'b'})和一个整数 kk ≥ 1)。要求找到 st 的一个最长公共子序列(LCS),满足以下条件:

  1. 子序列中所有属于 C 的字符必须按顺序连续出现至少 k(即每个 c ∈ C 在子序列中出现的连续段长度必须 ≥ k)。
  2. 子序列中每个字符 c ∈ C总出现次数不能超过给定的上限(例如 max_count[c])。

例如,若 s = "aabbcc", t = "abcabc", C = {'a', 'b'}, k = 2, max_count = {'a': 3, 'b': 2},则满足条件的 LCS 可能是 "aabb"(其中 'a' 连续出现 2 次,'b' 连续出现 2 次,且 'a' 总次数 2 ≤ 3,'b' 总次数 2 ≤ 2)。


解题过程

步骤 1:定义状态
在标准 LCS 的二维动态规划基础上,需要增加状态来跟踪 C 中字符的连续出现次数和总出现次数。
定义 dp[i][j][x][y]

  • i:考虑 s 的前 i 个字符(1-based indexing)。
  • j:考虑 t 的前 j 个字符。
  • x:当前子序列末尾字符的连续出现次数(若末尾字符不属于 C,则 x = 0)。
  • y:一个位掩码或整数,记录各 c ∈ C 的总出现次数(由于上限较小,可用多维数组或压缩状态)。

但直接这样定义状态空间可能过大。我们需要优化:

  • 仅当当前字符属于 C 时才记录连续次数,否则连续次数为 0。
  • 总次数限制可以通过预处理或状态转移时检查是否超限来避免记录全部组合。

简化状态设计
dp[i][j][last_char][cont_count][total_count],其中:

  • last_char:当前子序列末尾字符(用整数表示,0 表示无字符或字符不属于 C)。
  • cont_count:当前连续次数(仅当 last_char ∈ C 时有效)。
  • total_count:各 c ∈ C 的总次数数组(可压缩为整数,若上限小)。

但维度仍太高。进一步优化:

  • total_count 提取到外部循环,通过预处理所有可能的总次数组合,对每种组合单独做 DP。
  • 状态简化为 dp[i][j][last_char][cont_count],在转移时检查总次数是否超限。

步骤 2:状态转移方程
对于每个 (i, j) 和状态 (last_char, cont_count)

  1. 不选 s[i]t[j]
    dp[i][j][lc][cc] = max(dp[i-1][j][lc][cc], dp[i][j-1][lc][cc])
  2. s[i] == t[j],考虑将 s[i] 加入子序列:
    • s[i] ∉ C
      新状态 (last_char = 0, cont_count = 0),总次数不变。
      dp[i][j][0][0] = max(dp[i][j][0][0], dp[i-1][j-1][lc][cc] + 1)
    • s[i] ∈ C
      • last_char == s[i]
        新连续次数 new_cc = cont_count + 1
        检查 new_cc 是否满足 new_cc >= knew_cc == 1(允许刚开始的连续段)。
        总次数 new_total = total_count[s[i]] + 1 不能超限。
        dp[i][j][s[i]][new_cc] = max(...)
      • last_char != s[i]
        新连续次数 new_cc = 1
        检查总次数是否超限。
        dp[i][j][s[i]][1] = max(...)

步骤 3:初始化

  • dp[0][j][0][0] = 0 对所有 j
  • dp[i][0][0][0] = 0 对所有 i
  • 其他状态初始为 -inf(表示不可达)。

步骤 4:答案提取
遍历所有 i = |s|, j = |t|,以及所有 (last_char, cont_count),满足:

  • last_char ∈ C,则 cont_count >= k(保证末尾连续段也满足条件)。
  • 各字符总次数不超过上限。
    取最大值即为最长公共子序列长度。

步骤 5:复杂度优化
状态数 O(n * m * |C| * k)(因为连续次数只需记录到 k,超过 k 可合并为 k)。
总复杂度 O(n * m * |C| * k),在 |C|k 较小时可行。


示例验证
s = "aabb", t = "abab", C = {'a','b'}, k = 2, max_count = {'a':2, 'b':2} 为例:

  • 可能 LCS:"aabb"(长度 4,满足连续 ≥2 且总次数未超限)。
  • 通过 DP 计算可得最大长度为 4。

通过以上步骤,我们解决了带复杂限制的 LCS 变种问题。

线性动态规划:最长公共子序列的变种——带字符必须按顺序出现限制的最长公共子序列(进阶版:限制某些字符必须连续出现k次,且总出现次数有限制) 题目描述 给定两个字符串 s 和 t ,以及一个字符集合 C (例如 C = {'a', 'b'} )和一个整数 k ( k ≥ 1 )。要求找到 s 和 t 的一个最长公共子序列(LCS),满足以下条件: 子序列中所有属于 C 的字符必须 按顺序连续出现至少 k 次 (即每个 c ∈ C 在子序列中出现的连续段长度必须 ≥ k )。 子序列中每个字符 c ∈ C 的 总出现次数不能超过给定的上限 (例如 max_count[c] )。 例如,若 s = "aabbcc" , t = "abcabc" , C = {'a', 'b'} , k = 2 , max_count = {'a': 3, 'b': 2} ,则满足条件的 LCS 可能是 "aabb" (其中 'a' 连续出现 2 次, 'b' 连续出现 2 次,且 'a' 总次数 2 ≤ 3, 'b' 总次数 2 ≤ 2)。 解题过程 步骤 1:定义状态 在标准 LCS 的二维动态规划基础上,需要增加状态来跟踪 C 中字符的连续出现次数和总出现次数。 定义 dp[i][j][x][y] : i :考虑 s 的前 i 个字符(1-based indexing)。 j :考虑 t 的前 j 个字符。 x :当前子序列末尾字符的连续出现次数(若末尾字符不属于 C ,则 x = 0 )。 y :一个位掩码或整数,记录各 c ∈ C 的总出现次数(由于上限较小,可用多维数组或压缩状态)。 但直接这样定义状态空间可能过大。我们需要优化: 仅当当前字符属于 C 时才记录连续次数,否则连续次数为 0。 总次数限制可以通过预处理或状态转移时检查是否超限来避免记录全部组合。 简化状态设计 : 设 dp[i][j][last_char][cont_count][total_count] ,其中: last_char :当前子序列末尾字符(用整数表示,0 表示无字符或字符不属于 C )。 cont_count :当前连续次数(仅当 last_char ∈ C 时有效)。 total_count :各 c ∈ C 的总次数数组(可压缩为整数,若上限小)。 但维度仍太高。进一步优化: 将 total_count 提取到外部循环,通过 预处理所有可能的总次数组合 ,对每种组合单独做 DP。 状态简化为 dp[i][j][last_char][cont_count] ,在转移时检查总次数是否超限。 步骤 2:状态转移方程 对于每个 (i, j) 和状态 (last_char, cont_count) : 不选 s[i] 或 t[j] : dp[i][j][lc][cc] = max(dp[i-1][j][lc][cc], dp[i][j-1][lc][cc]) 。 当 s[i] == t[j] 时 ,考虑将 s[i] 加入子序列: 若 s[i] ∉ C : 新状态 (last_char = 0, cont_count = 0) ,总次数不变。 dp[i][j][0][0] = max(dp[i][j][0][0], dp[i-1][j-1][lc][cc] + 1) 。 若 s[i] ∈ C : 若 last_char == s[i] : 新连续次数 new_cc = cont_count + 1 。 检查 new_cc 是否满足 new_cc >= k 或 new_cc == 1 (允许刚开始的连续段)。 总次数 new_total = total_count[s[i]] + 1 不能超限。 dp[i][j][s[i]][new_cc] = max(...) 。 若 last_char != s[i] : 新连续次数 new_cc = 1 。 检查总次数是否超限。 dp[i][j][s[i]][1] = max(...) 。 步骤 3:初始化 dp[0][j][0][0] = 0 对所有 j 。 dp[i][0][0][0] = 0 对所有 i 。 其他状态初始为 -inf (表示不可达)。 步骤 4:答案提取 遍历所有 i = |s| , j = |t| ,以及所有 (last_char, cont_count) ,满足: 若 last_char ∈ C ,则 cont_count >= k (保证末尾连续段也满足条件)。 各字符总次数不超过上限。 取最大值即为最长公共子序列长度。 步骤 5:复杂度优化 状态数 O(n * m * |C| * k) (因为连续次数只需记录到 k ,超过 k 可合并为 k )。 总复杂度 O(n * m * |C| * k) ,在 |C| 和 k 较小时可行。 示例验证 以 s = "aabb" , t = "abab" , C = {'a','b'} , k = 2 , max_count = {'a':2, 'b':2} 为例: 可能 LCS: "aabb" (长度 4,满足连续 ≥2 且总次数未超限)。 通过 DP 计算可得最大长度为 4。 通过以上步骤,我们解决了带复杂限制的 LCS 变种问题。