最长公共子序列的变种:带通配符的最长公共子序列(进阶版:允许通配符匹配任意字符)
字数 3833 2025-10-29 23:21:20
最长公共子序列的变种:带通配符的最长公共子序列(进阶版:允许通配符匹配任意字符)
题目描述
给定两个字符串 s1 和 s2,其中 s1 可能包含通配符 *,它可以匹配零个或多个任意字符(包括空字符)。要求找出 s1 和 s2 的最长公共子序列(LCS),但需满足:s1 中的通配符可以匹配 s2 中的任意连续子序列(包括空序列)。例如:
- 输入:
s1 = "ab*c",s2 = "abdc"
输出:4("ab*c"中的*匹配s2的"d",LCS 为"abdc") - 输入:
s1 = "a*b",s2 = "ab"
输出:2(*匹配空序列,LCS 为"ab")
解题过程
-
定义状态
设dp[i][j]表示s1的前i个字符与s2的前j个字符的带通配符的 LCS 长度。i的范围是0到len(s1),j的范围是0到len(s2)。- 当
i=0或j=0时,一个字符串为空,LCS 长度为 0。
-
状态转移方程
根据s1[i-1](第i个字符)的类型分情况讨论:- 情况1:
s1[i-1]是普通字符- 若
s1[i-1] == s2[j-1],当前字符匹配,长度加1:
dp[i][j] = dp[i-1][j-1] + 1 - 否则,继承之前的最优解:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
- 若
- 情况2:
s1[i-1]是通配符*
通配符可匹配s2中任意连续子序列(包括空),因此有三种选择:- 匹配空序列:
dp[i][j] = dp[i-1][j](忽略*) - 匹配单个字符:
dp[i][j] = dp[i][j-1](*匹配s2[j-1],但*可继续匹配) - 匹配多个字符:
dp[i][j] = dp[i][j-1]已隐含多字符情况,因从j-1转移时已考虑之前匹配。
实际上可合并为:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
(注意:此处需确保*的匹配是连续的,但通过dp[i][j-1]的传递性可覆盖连续匹配)
- 匹配空序列:
- 情况1:
-
初始化
dp[0][j] = 0(空s1与任何s2的 LCS 为 0)dp[i][0] = 0(空s2与任何s1的 LCS 为 0,除非s1全为*,但题目求公共序列,空序列长度为 0)
-
遍历顺序与填表
从i=1到len(s1),j=1到len(s2)顺序填表,确保子问题已计算。 -
举例验证
以s1 = "ab*c",s2 = "abdc"为例:- 初始化:首行首列全 0。
- 填表:
i=1(a)匹配s2的a:dp[1][1] = 1i=2(b)匹配s2的b:dp[2][2] = 2i=3(*):j=3:max(dp[2][3]=0, dp[3][2]=2) = 2(*匹配空)j=4:max(dp[2][4]=2, dp[3][3]=2) = 2,但需注意*可匹配d,通过dp[3][3]传递。
i=4(c)匹配s2的c:dp[4][4] = dp[3][3] + 1 = 3?
错误!正确应为:当i=4, j=4,s1[3]='c'与s2[3]='c'匹配,但dp[3][3]是多少?
重新修正:
实际表中:dp[3][3] = max(dp[2][3], dp[3][2]) = max(0,2)=2(*在j=3时匹配空)dp[3][4] = max(dp[2][4], dp[3][3]) = max(2,2)=2(*匹配d通过dp[3][3]传递)dp[4][4] = dp[3][3] + 1 = 2+1=3?但正确答案应为 4。
问题在于通配符*匹配s2的多个字符时,需允许*匹配"d"后仍保留*的状态。
修正转移方程:
当s1[i-1]是*时,应允许三种操作:
- 忽略
*(匹配空):dp[i][j] = dp[i-1][j] - 用
*匹配当前字符s2[j-1],并保留*继续匹配:dp[i][j] = dp[i][j-1] - 用
*匹配当前字符后结束匹配:dp[i][j] = dp[i-1][j-1] + 1?不对,因为*可匹配多个字符,不能直接加1。
实际上,dp[i][j]应取三者最大:
dp[i][j] = max(dp[i-1][j], dp[i][j-1], dp[i-1][j-1] + 1)?但dp[i-1][j-1] + 1仅在*匹配单个字符时有效,而dp[i][j-1]已包含多字符情况。
正确做法:将*视为可扩展的匹配器,状态转移为:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
但需注意,当*匹配字符时,不应增加 LCS 长度(因为*不是实际字符)。
最终方程:
- 若
s1[i-1]是普通字符:按原 LCS 逻辑。 - 若
s1[i-1]是*:dp[i][j] = max(dp[i-1][j], dp[i][j-1])
重新计算例子:
表值如下(省略部分):
问题根源:s2: a b d c s1:0 0 0 0 0 a:0 1 1 1 1 b:0 1 2 2 2 *:0 1 2 2 2 // * 匹配 d 时,从 dp[2][2]=2 传递到 dp[3][3]=2 c:0 1 2 2 3 // 错误!正确答案应为4*匹配"d"时,应允许后续c匹配,但表中dp[3][3]=2导致dp[4][4]=3。
关键修正:通配符*匹配字符时,虽然不增加 LCS 长度,但应允许跳过s2的字符而不消耗s1的普通字符。
正确状态转移:- 普通字符匹配:同经典 LCS。
- 通配符
*:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
但需在匹配s1[i-1]和s2[j-1]时,若*匹配成功,可视为s1[i-1]与s2[j-1]匹配,但不增加长度?
更准确:*可以匹配任意子序列,因此dp[i][j]应取dp[i-1][j](跳过*)和dp[i][j-1](用*匹配当前字符)的最大值。
重新计算:
仍错误!正确发现:当s2: a b d c s1:0 0 0 0 0 a:0 1 1 1 1 b:0 1 2 2 2 *:0 1 2 2 2 // j=3 时,dp[3][3]=max(dp[2][3]=0, dp[3][2]=2)=2 c:0 1 2 2 3?i=4, j=4,s1[3]='c'与s2[3]='c'匹配,应取dp[3][3] + 1 = 2+1=3,但期望是 4。
说明*匹配"d"时,应记录匹配了字符,但传统定义下dp[i][j]仅记录长度,未区分是否消耗了s2的字符。
正解:此问题实为带通配符的字符串匹配问题,而非传统 LCS。需重新定义状态:
dp[i][j]表示s1前i字符能否匹配s2前j字符,但题目求最长公共子序列长度。
实际上,此题更接近 通配符匹配 的变种,但求的是匹配后的最长序列长度。
简化做法:将s1中的通配符视为可匹配任意字符的特殊字符,但匹配时不增加 LCS 长度。
修正状态转移:- 若
s1[i-1]是普通字符:- 匹配:
dp[i][j] = dp[i-1][j-1] + 1 - 不匹配:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
- 匹配:
- 若
s1[i-1]是*:
dp[i][j] = max(dp[i-1][j], dp[i][j-1], dp[i-1][j-1] + 1)?
但dp[i-1][j-1] + 1不合理,因为*不是实际字符。
正确理解:*匹配的字符不计入 LCS 长度,但允许后续字符继续匹配。
因此最终方程:
但此方程仍得不到正确答案 4。if s1[i-1] == '*': dp[i][j] = max(dp[i-1][j], dp[i][j-1]) else: if s1[i-1] == s2[j-1]: dp[i][j] = dp[i-1][j-1] + 1 else: dp[i][j] = max(dp[i-1][j], dp[i][j-1])
本质问题:此题实为求 s1 通配符匹配下与 s2 的最长公共子序列,但*可匹配任意子序列,因此最大长度实际上是s2的长度(若s1能匹配整个s2)。
重新审题:例子s1="ab*c", s2="abdc"的 LCS 是 4,因为s1匹配整个s2。
正确解法:将问题转化为 带通配符的字符串匹配,但求匹配部分的最长长度。
定义dp[i][j]为匹配长度,转移方程:- 若
s1[i-1]是普通字符且匹配s2[j-1],或s1[i-1]是*:
dp[i][j] = dp[i-1][j-1] + 1 - 同时,
*可匹配空或多个字符:
dp[i][j] = max(dp[i][j], dp[i-1][j], dp[i][j-1])
最终方程:
计算例子:dp[i][j] = max(dp[i-1][j], dp[i][j-1]) if s1[i-1] == '*' or s1[i-1] == s2[j-1]: dp[i][j] = max(dp[i][j], dp[i-1][j-1] + 1)
得到正确答案 4。s2: a b d c s1:0 0 0 0 0 a:0 1 1 1 1 b:0 1 2 2 2 *:0 1 2 3 3 // 注意 dp[3][3]=max(0,2, dp[2][2]+1=3)=3 c:0 1 2 3 4 // dp[4][4]=max(2,3, dp[3][3]+1=4)=4
-
复杂度分析
- 时间复杂度:O(mn),其中 m、n 为两字符串长度。
- 空间复杂度:可优化至 O(n)。