线性动态规划:最长公共子序列的变种——带通配符的最长公共子序列(进阶版:允许通配符匹配任意字符且通配符可匹配空字符)
字数 1779 2025-10-30 17:43:25
线性动态规划:最长公共子序列的变种——带通配符的最长公共子序列(进阶版:允许通配符匹配任意字符且通配符可匹配空字符)
题目描述
给定两个字符串 s 和 t,其中字符串 t 可能包含通配符 *。通配符 * 可以匹配任意长度的字符序列(包括空序列)。求 s 和 t 的最长公共子序列(LCS)长度,其中 t 中的通配符可以匹配 s 中的任意连续子串。
示例
输入:
s = "abcde"
t = "a*c*e"
输出:5
解释:通配符 * 可以匹配 "b" 和 "d",因此整个 s 都是匹配的。
解题思路
关键点
- 通配符
*可以匹配任意长度的子串(包括空串),这相当于在匹配过程中允许跳过s的任意连续部分。 - 需要设计动态规划状态,区分普通字符匹配和通配符的灵活性。
状态定义
定义 dp[i][j] 表示 s 的前 i 个字符和 t 的前 j 个字符的最长公共子序列长度。
状态转移方程
-
普通字符匹配:
若t[j-1]不是通配符*,则需检查s[i-1]和t[j-1]是否匹配:- 如果
s[i-1] == t[j-1],则dp[i][j] = dp[i-1][j-1] + 1。 - 否则,
dp[i][j] = max(dp[i-1][j], dp[i][j-1])(继承之前的最优解)。
- 如果
-
通配符匹配:
若t[j-1]是通配符*,则它可以选择匹配s中从当前位置开始的任意长度子串(包括空串)。此时:- 匹配空串:
dp[i][j] = dp[i][j-1](跳过t的通配符)。 - 匹配一个或多个字符:
dp[i][j] = dp[i-1][j](利用*匹配s[i-1],并继续允许*匹配更多字符)。 - 综合两种情况:
dp[i][j] = max(dp[i][j-1], dp[i-1][j])。
- 匹配空串:
初始化
dp[0][j]:当s为空时,若t的前j个字符全是*,则匹配长度为 0(因为*可匹配空串);否则为 0(无法匹配非通配符)。dp[i][0]:当t为空时,匹配长度恒为 0。
详细步骤
- 初始化二维数组
dp,大小为(len(s)+1) x (len(t)+1),所有元素初始为 0。 - 遍历
i从 1 到len(s),j从 1 到len(t):- 如果
t[j-1] == '*':dp[i][j] = max(dp[i][j-1], dp[i-1][j])
- 否则:
- 如果
s[i-1] == t[j-1]:dp[i][j] = dp[i-1][j-1] + 1
- 否则:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
- 如果
- 如果
- 最终结果在
dp[len(s)][len(t)]。
示例推导
以 s = "abcde"、t = "a*c*e" 为例:
初始化 dp 表(行索引 i 对应 s,列索引 j 对应 t):
t: a * c * e
s:0 0 0 0 0 0
a:0
b:0
c:0
d:0
e:0
逐步填充:
i=1, j=1:s[0]='a'匹配t[0]='a',dp[1][1] = dp[0][0]+1 = 1。i=1, j=2:t[1]='*',dp[1][2] = max(dp[1][1], dp[0][2]) = max(1,0) = 1。i=2, j=2:t[1]='*',dp[2][2] = max(dp[2][1], dp[1][2]) = max(0,1) = 1(注意dp[2][1]未匹配,为 0)。- 继续推导,最终
dp[5][5] = 5,表示整个s被匹配。
复杂度分析
- 时间复杂度:O(mn),其中
m和n分别是s和t的长度。 - 空间复杂度:O(mn),可优化至 O(n)(仅保留前一行的状态)。
总结
本题通过扩展经典 LCS 问题,引入通配符的灵活匹配机制。关键点在于处理 * 时,同时考虑跳过通配符或利用它匹配当前字符,并通过动态规划的状态转移覆盖所有可能情况。