最长公共子序列的变种:带通配符和间隔限制的最长公共子序列(允许通配符匹配任意字符,且匹配字符之间允许最多跳过k个字符)
字数 2922 2025-12-05 08:11:39

最长公共子序列的变种:带通配符和间隔限制的最长公共子序列(允许通配符匹配任意字符,且匹配字符之间允许最多跳过k个字符)

题目描述
给定两个字符串 s1s2,以及一个非负整数 k。字符串中可能包含通配符 *,它可以匹配任意字符(包括空字符)。现在要求从 s1s2 中分别取出一个子序列,使得这两个子序列相等,并且满足以下条件:

  1. 子序列中的字符必须按原字符串中的顺序出现。
  2. 当匹配字符时,如果字符相同,或者其中一个字符是通配符 *,则视为匹配成功。
  3. 匹配的两个字符在各自原字符串中的位置索引差(即跳过字符的数量)不能超过 k。具体来说,假设从 s1 中取出的字符位置为 i,从 s2 中取出的字符位置为 j,且这是子序列中相邻的两个匹配对,则必须满足 |i - 前一个匹配的i| <= k|j - 前一个匹配的j| <= k。换句话说,在匹配的子序列中,任意两个相邻匹配的字符在各自字符串中的距离(跳过字符数)不能超过 k
    求满足条件的最长子序列的长度。

示例
输入:

s1 = "a*b*c"
s2 = "adbpc"
k = 2

解释:

  • 通配符 * 可以匹配任意字符。
  • 最长公共子序列可以是 "abc",匹配方式为:
    s1中的 'a'(索引0)匹配 s2中的 'a'(索引0)
    s1中的 ''(索引1)匹配 s2中的 'd'(索引1)
    s1中的 'b'(索引2)匹配 s2中的 'b'(索引3)
    s1中的 '
    '(索引3)匹配 s2中的 'p'(索引4)
    s1中的 'c'(索引4)匹配 s2中的 'c'(索引5)
    但注意:相邻匹配的索引差不能超过 k=2。检查:
    s1中索引从0到1差为1(≤2),s2中索引从0到1差为1(≤2);
    s1中索引从1到2差为1(≤2),s2中索引从1到3差为2(≤2);
    s1中索引从2到3差为1(≤2),s2中索引从3到4差为1(≤2);
    s1中索引从3到4差为1(≤2),s2中索引从4到5差为1(≤2)。
    满足条件,所以长度为5。
    如果没有间隔限制(k很大),整个字符串都可以匹配,长度为5。但如果 k=1,则 s2 中 'b' 到 'c' 的索引差为2>1,无法全部匹配,最长可能是4(例如匹配 "ab" 和 "adbp")。

输出:

5

解题步骤
这是一个线性动态规划问题,但状态定义需要同时考虑两个字符串的索引,并且增加一个维度来记录上一个匹配的位置以满足间隔限制。为了降低复杂度,我们采用三维DP。

步骤1:定义状态
dp[i][j][p] 表示考虑 s1 的前 i 个字符和 s2 的前 j 个字符,且最后一个匹配的字符在 s1 中的索引是 p 的情况下,所能得到的最长公共子序列长度。
但这样状态会很大(O(n³)),且难以处理间隔限制。我们需要更巧妙的设计。

另一种思路:
定义 dp[i][j] 表示以 s1[i]s2[j] 作为匹配的最后一对字符时,所能得到的最长公共子序列长度。然后,在转移时,我们需要检查之前所有可能的匹配对 (i', j'),满足 i' < ij' < ji - i' <= kj - j' <= k。这样复杂度是 O(n² * k²),在k较小时可行。

步骤2:转移方程
如果 s1[i]s2[j] 可以匹配(即字符相等或有一个是通配符 *),则:

dp[i][j] = 1 + max{ dp[i'][j'] }  (其中 i' 满足 0 <= i' < i 且 i - i' <= k, j' 满足 0 <= j' < j 且 j - j' <= k)

如果 s1[i]s2[j] 不能匹配,则 dp[i][j] = 0
注意:如果之前没有匹配对,则取1(即只匹配当前这对字符)。

步骤3:初始化
如果 s1[i]s2[j] 可匹配,则至少可以形成一个长度为1的子序列,所以 dp[i][j] 至少为1。我们可以初始化为0,然后在可匹配时先赋值为1,再尝试从之前的状态转移。

步骤4:最终答案
最终答案是所有 dp[i][j] 的最大值,因为最长公共子序列可能以任意一对字符作为结尾。

步骤5:复杂度优化
直接枚举所有 i', j' 会导致 O(n² * k²) 的复杂度。如果k很大(接近n),则为 O(n⁴),不可接受。但我们可以用滑动窗口最大值来优化:
对于固定的 i, j,我们需要在矩阵 dp 中区域 [i-k, i-1] x [j-k, j-1] 内找最大值。这可以分解为:
先对每个 i,维护一个关于 j 的滑动窗口最大值(窗口大小k);再对每个 j,维护一个关于 i 的滑动窗口最大值。但这里是二维区域最大值,比较棘手。
一个更简单的方法:由于k通常不会很大,我们可以接受 O(n² * k) 的复杂度,即固定 i, j 后,枚举 i'max(0, i-k)i-1,然后对每个 i',我们需要在 j'max(0, j-k)j-1 中找最大的 dp[i'][j']。我们可以提前用另一个二维数组 rowMax[i'][j] 记录对于固定的 i',在 j 的滑动窗口内的最大值。这样,对于每个 (i, j),枚举 i' 时直接取 rowMax[i'][j-1] 即可。复杂度 O(n² * k)。

具体实现步骤

  1. 初始化 dp 为全0,rowMax 为全0。
  2. 遍历 i 从0到 len(s1)-1,遍历 j 从0到 len(s2)-1。
  3. 如果 s1[i]s2[j] 可匹配(相等或任意一个是 *):
    a. 先设 dp[i][j] = 1
    b. 枚举 i'max(0, i-k)i-1
    prevJ = max(0, j-k)j-1 范围内的最大值,即 rowMax[i'][j-1](注意处理边界)。
    如果 rowMax[i'][j-1] > 0,则 dp[i][j] = max(dp[i][j], 1 + rowMax[i'][j-1])
  4. 更新 rowMax[i][j]max(rowMax[i][j-1], dp[i][j])(当 j>0),否则为 dp[i][j]
  5. 同时更新全局答案 ans = max(ans, dp[i][j])
  6. 最终返回 ans

边界情况处理

  • 如果 k=0,则只允许字符一一对应匹配,没有跳过,相当于普通LCS但带通配符。
  • 通配符 * 可以匹配任意字符,包括另一个通配符。
  • 注意字符串可能为空,此时答案为0。

示例推演
s1="a*b*c", s2="adbpc", k=2 为例。
略去详细表格,但按上述算法,最终 dp[4][5] 会得到5,对应整个字符串匹配。

总结
这道题是LCS的变种,增加了通配符匹配和相邻匹配字符的间隔限制。通过三维状态的压缩和滑动窗口优化,可以在合理时间内求解。关键点是设计状态转移时考虑间隔限制,并利用辅助数组降低复杂度。

最长公共子序列的变种:带通配符和间隔限制的最长公共子序列(允许通配符匹配任意字符,且匹配字符之间允许最多跳过k个字符) 题目描述 给定两个字符串 s1 和 s2 ,以及一个非负整数 k 。字符串中可能包含通配符 * ,它可以匹配任意字符(包括空字符)。现在要求从 s1 和 s2 中分别取出一个子序列,使得这两个子序列相等,并且满足以下条件: 子序列中的字符必须按原字符串中的顺序出现。 当匹配字符时,如果字符相同,或者其中一个字符是通配符 * ,则视为匹配成功。 匹配的两个字符在各自原字符串中的位置索引差(即跳过字符的数量)不能超过 k 。具体来说,假设从 s1 中取出的字符位置为 i ,从 s2 中取出的字符位置为 j ,且这是子序列中相邻的两个匹配对,则必须满足 |i - 前一个匹配的i| <= k 且 |j - 前一个匹配的j| <= k 。换句话说,在匹配的子序列中,任意两个相邻匹配的字符在各自字符串中的距离(跳过字符数)不能超过 k 。 求满足条件的最长子序列的长度。 示例 输入: 解释: 通配符 * 可以匹配任意字符。 最长公共子序列可以是 "abc" ,匹配方式为: s1中的 'a'(索引0)匹配 s2中的 'a'(索引0) s1中的 ' '(索引1)匹配 s2中的 'd'(索引1) s1中的 'b'(索引2)匹配 s2中的 'b'(索引3) s1中的 ' '(索引3)匹配 s2中的 'p'(索引4) s1中的 'c'(索引4)匹配 s2中的 'c'(索引5) 但注意:相邻匹配的索引差不能超过 k=2。检查: s1中索引从0到1差为1(≤2),s2中索引从0到1差为1(≤2); s1中索引从1到2差为1(≤2),s2中索引从1到3差为2(≤2); s1中索引从2到3差为1(≤2),s2中索引从3到4差为1(≤2); s1中索引从3到4差为1(≤2),s2中索引从4到5差为1(≤2)。 满足条件,所以长度为5。 如果没有间隔限制(k很大),整个字符串都可以匹配,长度为5。但如果 k=1,则 s2 中 'b' 到 'c' 的索引差为2>1,无法全部匹配,最长可能是4(例如匹配 "a b " 和 "adbp")。 输出: 解题步骤 这是一个线性动态规划问题,但状态定义需要同时考虑两个字符串的索引,并且增加一个维度来记录上一个匹配的位置以满足间隔限制。为了降低复杂度,我们采用三维DP。 步骤1:定义状态 设 dp[i][j][p] 表示考虑 s1 的前 i 个字符和 s2 的前 j 个字符,且 最后一个匹配的字符在 s1 中的索引是 p 的情况下,所能得到的最长公共子序列长度。 但这样状态会很大(O(n³)),且难以处理间隔限制。我们需要更巧妙的设计。 另一种思路: 定义 dp[i][j] 表示以 s1[i] 和 s2[j] 作为匹配的最后一对字符时,所能得到的最长公共子序列长度。然后,在转移时,我们需要检查之前所有可能的匹配对 (i', j') ,满足 i' < i 且 j' < j 且 i - i' <= k 且 j - j' <= k 。这样复杂度是 O(n² * k²),在k较小时可行。 步骤2:转移方程 如果 s1[i] 和 s2[j] 可以匹配(即字符相等或有一个是通配符 * ),则: 如果 s1[i] 和 s2[j] 不能匹配,则 dp[i][j] = 0 。 注意:如果之前没有匹配对,则取1(即只匹配当前这对字符)。 步骤3:初始化 如果 s1[i] 和 s2[j] 可匹配,则至少可以形成一个长度为1的子序列,所以 dp[i][j] 至少为1。我们可以初始化为0,然后在可匹配时先赋值为1,再尝试从之前的状态转移。 步骤4:最终答案 最终答案是所有 dp[i][j] 的最大值,因为最长公共子序列可能以任意一对字符作为结尾。 步骤5:复杂度优化 直接枚举所有 i', j' 会导致 O(n² * k²) 的复杂度。如果k很大(接近n),则为 O(n⁴),不可接受。但我们可以用滑动窗口最大值来优化: 对于固定的 i, j ,我们需要在矩阵 dp 中区域 [i-k, i-1] x [j-k, j-1] 内找最大值。这可以分解为: 先对每个 i ,维护一个关于 j 的滑动窗口最大值(窗口大小k);再对每个 j ,维护一个关于 i 的滑动窗口最大值。但这里是二维区域最大值,比较棘手。 一个更简单的方法:由于k通常不会很大,我们可以接受 O(n² * k) 的复杂度,即固定 i, j 后,枚举 i' 从 max(0, i-k) 到 i-1 ,然后对每个 i' ,我们需要在 j' 从 max(0, j-k) 到 j-1 中找最大的 dp[i'][j'] 。我们可以提前用另一个二维数组 rowMax[i'][j] 记录对于固定的 i' ,在 j 的滑动窗口内的最大值。这样,对于每个 (i, j) ,枚举 i' 时直接取 rowMax[i'][j-1] 即可。复杂度 O(n² * k)。 具体实现步骤 初始化 dp 为全0, rowMax 为全0。 遍历 i 从0到 len(s1)-1,遍历 j 从0到 len(s2)-1。 如果 s1[i] 和 s2[j] 可匹配(相等或任意一个是 * ): a. 先设 dp[i][j] = 1 。 b. 枚举 i' 从 max(0, i-k) 到 i-1 : 取 prevJ = max(0, j-k) 到 j-1 范围内的最大值,即 rowMax[i'][j-1] (注意处理边界)。 如果 rowMax[i'][j-1] > 0 ,则 dp[i][j] = max(dp[i][j], 1 + rowMax[i'][j-1]) 。 更新 rowMax[i][j] 为 max(rowMax[i][j-1], dp[i][j]) (当 j>0),否则为 dp[i][j] 。 同时更新全局答案 ans = max(ans, dp[i][j]) 。 最终返回 ans 。 边界情况处理 如果 k=0,则只允许字符一一对应匹配,没有跳过,相当于普通LCS但带通配符。 通配符 * 可以匹配任意字符,包括另一个通配符。 注意字符串可能为空,此时答案为0。 示例推演 以 s1="a*b*c" , s2="adbpc" , k=2 为例。 略去详细表格,但按上述算法,最终 dp[4][5] 会得到5,对应整个字符串匹配。 总结 这道题是LCS的变种,增加了通配符匹配和相邻匹配字符的间隔限制。通过三维状态的压缩和滑动窗口优化,可以在合理时间内求解。关键点是设计状态转移时考虑间隔限制,并利用辅助数组降低复杂度。