给定一文本字符串 std::string s
, 以及一个 pattern 串 std::string p
, 试判定 s
与 p
是否匹配。
假设文本串只被允许包含大小写英文字母,而 pattern 串除了大小写英文字母外还被允许包含字符类符号,在这些字符类符号中,’*’
代表任意有限长的由大小写英文字母组成的字符串,’?’
代表任意单个大小写英文字符;
所谓的「s
与 p
匹配」指的是:如果 p
全由大小写英文字母组成,那么 p
和 s
匹配当且仅当表达式 p == s
的求值结果为 true
;
若不然,若 p
中有 ‘*’
或者 ‘?’
这样的字符类,则我们说 p
与 s
是匹配的当且仅当能够对 p
中的 ‘*’
与 ‘?’
字符进行有限次替换使得 p == s
.
对于有正则表达式应用经验的读者来说,’*’
其实可以看作是「通配符」,能够匹配 0 长字符子串,也能够匹配任意有限长的字符子串,’?’
也可以看作是通配符,只不过它相比于 ‘*’
只能够匹配长度为 1 的字符子串。
以 std::string s = "aabca"
, std::string p = "a*c?"
举例,我们可以让 p
的 '*'
替换为 "ab"
, 再让 '?'
替换为 'a'
, 从而将 p
替换为 "aabca"
, 从而我们说 p
和 s
是匹配的。
以 std::string s = "cbbc"
, std::string p = "?**a"
举例,无论我们让 p
中的(两个)’*’
被替换为何物,让 '?'
被替换何物,都无法从 p
得到 s
, 这时我们可以说 p
和 s
是不匹配的。
上述的讨论除了帮助我们更明确地进一步理解问题本身之外,还向我们揭示了一个判定 p
与 s
是否匹配的思路:想象我们是在一个字符一个字符地扫描 p
中的字符(字符类)和 s
中的字符,指针 intptr_t i
指向 s
中正在被扫描到的字符,指针 intptr_t j
指向 p
中正在被扫描到的字符(字符类),若 p[j]
是大小写英文字母,则如果 s[i] == p[j]
, 就可以让 i
, j
都自增,如果 p[j] == '?'
, 由于 '?'
匹配任何字符,所以也相当于 s[i] == p[j]
, 也自增 i
和 j
, 如果 p[j] == '*'
, 我们就尝试让这个 '*'
分别匹配不同长度的 s
自 i
开始的 n
长子串,这个 n
可以分别尝试 0 到 N-i.
以下是 C++ 实现:
/**
* 返回 s 指向的字符串和 p 指向的字符串是否匹配。
*
* 参数说明:
* 1. s 指向文本串的首字符(如果该文本串非空),或者指向一个值为 0 的地址(代表空串);
* 2. p 指向 pattern 串的首字符(如果该 pattern 串非空),或者指向一个值为 0 的地址(代表空 pattern);
* 3. i 表示递归调用时当前 s 的首字符偏移量:s[i] 表示文本串的第一个字符(如果有的话);
* 4. j 表示递归调用时当前 p 的首字符偏移量:p[j] 表示 pattern 串的第一个字符(如果有的话);
* 5. N 表示文本串长度,我们用 i < N 来判断当前文本串是否是非空的;
* 6. M 表示 pattern 串的长度,我们用 j < M 来判定当前 pattern 串是否是非空的。
*
* 运行结果示例:
* <https://leetcode.com/submissions/detail/753424879/>
*
* 额外说明:
* 1. 空 pattern 匹配且只能够匹配空串。
*/
bool isMatchRecursive(char *s, char *p, size_t i, size_t j, size_t N, size_t M) {
while (j < M) {
if (p[j] == '*') {
// 当有连续多个 '*' 时,可以等价地看成只有 1 个 '*', 效果是一样的。
while (j < M && p[j] == '*')
++j;
// 分别尝试让 '*' 匹配不同长度的 s 中的子串,然后再让剩下的 p 匹配剩下的 s.
for (size_t n = 0; i+n <= N; ++n)
if (isMatchRecursive(s, p, i+n, j, N, M))
return true;
// 尝试了让 '*' 分别匹配所有可能长度的子串,余下的 p 都无法匹配余下的 s, 则失败。
return false;
} else if (i < N && (p[j] == '?' || s[i] == p[j])) {
// 匹配单个字符
++i;
++j;
} else {
// 不匹配
return false;
}
}
return i==N && j==M;
}
/**
* 返回文本串 s 与 pattern 串 p 是否匹配。
*/
bool isMatch(std::string s, std::string p) {
return isMatchRecursive(s.data(), p.data(), 0, 0, s.size(), p.size());
}
以下 testcase 能够使得上列代码运行超时(LeetCode 报 TLE 错误):
char *s = "abbabaaabbabbaababbabbbbbabbbabbbabaaaaababababbbabababaabbababaabbbbbbaaaabababbbaabbbbaabbbbababababbaabbaababaabbbababababbbbaaabbbbbabaaaabbababbbbaababaabbababbbbbababbbabaaaaaaaabbbbbaabaaababaaaabb";
char *p = "**aa*****ba*a*bb**aa*ab****a*aaaaaa***a*aaaa**bbabb*b*b**aaaaaaaaa*a********ba*bbb***a*ba*bb*bb**a*b*bb";
基本上,你可以理解为这样的解法它的时间复杂度是指数级别的。
考虑: