带通配符的字符串匹配问题(支持 '?' 和 '*' 以及字符类 '[abc]' 和否定类 '[!abc]')
字数 2062 2025-12-13 20:07:03
带通配符的字符串匹配问题(支持 '?' 和 '*' 以及字符类 '[abc]' 和否定类 '[!abc]')
问题描述
给定一个字符串 s 和一个模式串 p,其中 p 可能包含以下特殊字符:
'?':可以匹配任意单个字符。'*':可以匹配任意长度的字符序列(包括空序列)。'[abc]':可以匹配字符'a'、'b'或'c'中的任意一个。'[!abc]':可以匹配不在'a'、'b'、'c'中的任意单个字符。
问:模式串 p 是否能够完全匹配字符串 s?
示例:
s = "aab",p = "c*a*b"→ 输出false(因为 'c' 开头无法匹配)。s = "aab",p = "a*?"→ 输出true('*' 匹配 "ab",'?' 匹配 'b')。s = "abc",p = "a[bc]c"→ 输出true('[bc]' 匹配 'b')。s = "abc",p = "a[!b]c"→ 输出false('[!b]' 不能匹配 'b')。
解题思路
这是一个经典的字符串匹配问题,可以通过区间动态规划(本质上是一个二维 DP)解决。我们定义状态 dp[i][j] 表示:字符串 s 的前 i 个字符(即 s[0..i-1])是否与模式串 p 的前 j 个字符(即 p[0..j-1])匹配。目标是求 dp[m][n],其中 m = s.length(), n = p.length()。
状态转移步骤
-
初始化
dp[0][0] = true:空字符串匹配空模式。- 对于模式开头的连续
'*',因为'*'可以匹配空序列,所以如果p的前k个字符都是'*',则dp[0][k] = true(表示空字符串可以被这些模式匹配)。
-
状态转移(考虑
s的第i个字符s[i-1]和模式p的第j个字符p[j-1])- 如果
p[j-1]是普通字符(不是特殊字符):
只有当s[i-1] == p[j-1]且dp[i-1][j-1] == true时,dp[i][j] = true。 - 如果
p[j-1]是'?':
可以匹配任意单个字符,所以只要dp[i-1][j-1] == true,dp[i][j] = true。 - 如果
p[j-1]是'*':
有两种情况使dp[i][j] = true:
a)'*'匹配空序列:dp[i][j-1] == true(即不使用'*')。
b)'*'匹配当前字符s[i-1]并继续匹配:dp[i-1][j] == true(即使用'*'匹配当前字符,并保留'*'以便继续匹配更多字符)。 - 如果
p[j-1]是'['(表示字符类的开始):
需要找到对应的']'作为字符类的结束,然后检查s[i-1]是否在字符类内(或对于否定类是否不在类内)。
假设字符类为p[j-1..k],其中p[k] == ']'。- 如果字符类以
'!'开头(例如"[!abc]"):
当s[i-1]不在字符类中且dp[i-1][k] == true时,dp[i][k+1] = true(注意:匹配完整个字符类后,模式指针应跳到k+1)。 - 否则(例如
"[abc]"):
当s[i-1]在字符类中且dp[i-1][j-1] == true时,dp[i][k+1] = true。
因为字符类可能包含多个字符,我们需要提前解析模式串,记录每个'['对应的']'位置,以便在 DP 时直接跳转。
- 如果字符类以
- 如果
-
优化细节
- 解析字符类:可以预处理一个数组
matchClass[j],表示当p[j]是'['时,对应的']'的位置。 - 对于否定类,检查字符不在类中时,要注意转义字符(本问题不涉及转义)。
- 由于
'*'可以匹配任意序列,如果模式中有多个连续的'*',可以合并为一个'*'以优化。
- 解析字符类:可以预处理一个数组
最终答案
dp[m][n] 即为结果。时间复杂度 O(m × n),空间复杂度 O(m × n)(可优化到 O(n))。
示例验证
以 s = "abc", p = "a[bc]c" 为例:
dp[1][1] = true('a' 匹配 'a')。- 遇到
'[',字符类为"[bc]",匹配到'b',dp[2][4] = true(跳过字符类)。 - 最后
'c'匹配'c',dp[3][5] = true,所以完全匹配。