通配符匹配问题: https://leetcode.com/problems/wildcard-matching/
Given an input string (s) and a pattern (p), implement wildcard pattern matching with support for ‘?’ and ‘*’.
‘?’ Matches any single character. ‘*’ Matches any sequence of characters (including the empty sequence).
The matching should cover the entire input string (not partial).
Note:
s could be empty and contains only lowercase letters a-z. p could be empty and contains only lowercase letters a-z, and characters like ? or *.
待匹配字串 s 中只有字母,模式串中 p 只含有字母和 ?、*,? 匹配任意单个字符,* 匹配任意多个字符(包括空字串),判断字串是否完全匹配。
使用递归算法会超时。
动态规划
设 $f(i, j)$ 表示 s 前 $i$ 个字符与 p 前 $j$ 个字符是否匹配,当 $i \le 1$ 时不难写出递推公式:
$$f(i, j) = \begin{cases} false & \text{s[i - 1] 与 p[j - 1] 不匹配} \\ f(i - 1, j - 1) & \text{s[i - 1] 与 p[j - 1] 匹配} \\ f(i, j - 1) 或 f(i - 1, j) & \text{p[i - 1] 为 *} \end{cases}$$
对边界情况提前做特殊处理即可。C 语言实现:
bool isMatch_44_1(char *s, char *p) {
size_t const m = strlen(s), n = strlen(p);
bool **match = (bool **) malloc((m + 1) * sizeof(bool *));
for (size_t i = 0; i <= m; ++i)
match[i] = (bool *) calloc(n + 1, sizeof(bool));
match[0][0] = true;
for (size_t j = 1; j <= n && p[j - 1] == '*'; ++j)
match[0][j] = true;
for (size_t i = 1; i <= m; ++i) {
for (size_t j = 1; j <= n; ++j) {
if (p[j - 1] == '?' || p[j - 1] == s[i - 1])
match[i][j] = match[i - 1][j - 1];
else if (p[j - 1] == '*')
match[i][j] = match[i][j - 1] || match[i - 1][j];
}
}
bool ret = match[m][n];
for (size_t i = 0; i <= m; ++i) free(match[i]);
free(match);
return ret;
}时间复杂度为 $O(mn)$。
贪心法
递归版本实现可能出现以下逻辑:
if (p[j] == '*')
return match(i, j + 1) || match(i + 1, j);
这里 match(i, j) 表示从 s[i]、p[j] 开始是否匹配。
可见,每当遇到一个 *,都会产生新的分支。
而贪心法只对模式串上一个(已扫描部分的最后一个)* 进行继续搜索,只需记录上一个 * 指针即可。
C 语言实现:
bool isMatch_44_2(char *s, char *p) {
char *star = NULL, *last_s = NULL;
while (*s != '\0') {
if (*p == '?' || *p == *s) {
++s;
++p;
} else if (*p == '*') {
last_s = s;
star = p;
++p;
} else if (star != NULL) {
s = ++last_s;
p = star + 1;
} else {
return false;
}
}
while (*p == '*') ++p;
return *p == '\0';
}这样,分支明显少于递归版本。
以下是不严格说明:
i
[match][*]abc[*][to match]
[match] * abc * [to match]
第一行为 s,第二行为 p。[match] 表示已匹配子串,[to match] 表示待匹配子串。s 中的 [*] 与 p 中的 * 匹配。abc 是一段匹配的字符串(可能含有 ?)。
设 s 中 c 的下标为 i。如果回溯第一个 *,即让它匹配更长的子串,则新匹配(如果有的话)的 c 位置一定在 i 之后,它可能在 [*] 中,也可能在 [to match] 中,但无论如何,这些情况都被“只回溯第二个 * ”包括在内了。因此,只回溯最后一个 * 就可以了。
在最坏情况下,这一算法的时间复杂度也为 $O(mn)$。
实现源码
https://github.com/qianbinbin/leetcode