算法设计与分析 第十一周
通配符匹配
1 题目描述
2 选题原因
选择一道动态规划的题目,题目比较新颖,也想了比较久。动态规划的方法感觉逻辑上简单了不少,但是效率上却有些欠缺。
3 题目分析及算法
3.1 常规分析
首先要思考的是能够分解成什么样的子问题。最基本的思路是匹配不同长度的部分。
按照这样的思路,我们选取C(x, y)
表示第一个字符串的前x
长度和第二个字符串的前y
长度能否匹配,那么他又和前面的字符串有什么关系呢?
一般情况下,我们能够得到的情况就有几种:
- 1、匹配字符是
?
- 2、匹配字符是
*
- 3、匹配字符是字符且相同
- 4、匹配字符是字符且不同
依次考虑几种情况,很明显如果是1、2、3,那么肯定是可以匹配的,这时,只要
C(x - 1, y - 1)
是匹配即可。
4则一定是不匹配的。
3.2 问题所在
但是按照这样分析很快就发现了有问题:如果是1、3种情况,一定是匹配并且会占用一个字符的。但是情况2就复杂得多。
我们试想:aa
与*
的匹配情况。正常情况下,是可以匹配的。但是如果我们单纯的使用C(x - 1, y - 1)
来判断就会出错。因为毫无疑问C(1, 0)
是错误的。
为了避免这样的情况,我们可以设想,哪些情况下能够保证*
匹配一定是正确的。
- 1、毫无疑问
C(x - 1, y - 1) = true
时,一定是成立的。- 2、
C(x, y - 1) = true
时也是成立的。此时,*
将匹配一个空字符。- 3、
C(x - 1, y) = true
时也是成立的。此时,*
匹配的串将再加入x
位置的字符。
因此,我们可以总结出两条规则:
- 1、当
p[y] = '?' || p[y] == s[x]
时,只要C(x - 1, y - 1)
能够匹配,就是匹配的。- 2、当
p[y] = '*'
时,只要C(x - 1, y - 1) || C(x - 1, y) || C(x, y - 1)
能够匹配,就是匹配的。
3.3 初始化问题
这道题的初始化其实是很有意思的。首先我们思考,
p[0]
能够和谁匹配,答案只能和``字符匹配!s
中是不可能出现*
的!因此,我们实际上需要匹配的只是一行(或一列)。思考,怎么样匹配呢?道理是一样的,首先,第0个字符(空字符)和第0个字符一定是匹配的!之后呢?当p[i] = '*'
的时候,那么只要前面是匹配的,就是匹配的。(由于只能参照前面,斜上方和上方是空的)
3.4 算法
取字符串
s
长m
,匹配字符串p
长n
.
初始化:
C(0, 0) = true;
FOR i = 1 : m
- IF p[i - 1] = * THEN C(i, 0) = C(i - 1, 0)
//动态规划
FOR i = 1 : m- FOR j = 1 : n
- IF p[j - 1] = ‘?’ || p[j - 1] = s[i - 1] THEN C(i, j) = C(i - 1, j - 1)
- ELSE IF p[j - 1] = ‘*’ && (C(i - 1, j - 1) || C(i, j - 1) || C(i - 1, j)) THEN C(i, j) = true
- END FOR
END FOR
4 关键代码
4.1 初始化
bool matrix[m + 1][n + 1] = {{0}};
//初始化
//只需要初始化p[0]一行
matrix[0][0] = true;
//如果当前字符不是*,一定是错误的
for (int i = 1; i <= n; i++) {
if (p.at(i - 1) == '*') {
matrix[0][i] = matrix[0][i - 1];
} else {
matrix[0][i] = false;
}
}
4.2 动态规划
//动态规划
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (s.at(i - 1) == p.at(j - 1) || p.at(j - 1) == '?') { //当前字符一定额能够匹配
matrix[i][j] = matrix[i - 1][j -1];
} else if (p.at(j - 1) == '*' && (matrix[i - 1][j - 1] || matrix[i][j - 1] || matrix[i - 1][j])) { //当前字符可能和前面的匹配
matrix[i][j] = true;
}
}
}
return matrix[m][n];
4.3 防止错误输入
我们考虑,可能会出现输入的字符串为空的这种情况,于是就与先把他处理了。
考虑两种情况:
- 1、
s
为空:此时如果p
也为空,一定匹配;否则只要p
中有非*
字符,一定错。- 2、
p
为空:此时如果s
也为空,一定匹配,否则一定不匹配。
//判断字符串为空字符串
if (s.size() == 0) {
if (p.size() == 0) { //两个字符串都是空,那么一定相等
return true;
} else { //否则只要有字符不是*,就一定不等
for (string::iterator it = p.begin(); it != p.end(); it++) {
if (*it != '*') {
return false;
}
}
return true;
}
}
//如果匹配字符串为空串
if (p.size() == 0) {
if (s.size() == 0) { //如果字符串为空,那么一定相等
return true;
} else { //字符串不为空,一定不等
return false;
}
}
5 运行结果
6 源代码
class Solution {
public:
bool isMatch(string s, string p) {
//判断字符串为空字符串
if (s.size() == 0) {
if (p.size() == 0) { //两个字符串都是空,那么一定相等
return true;
} else { //否则只要有字符不是*,就一定不等
for (string::iterator it = p.begin(); it != p.end(); it++) {
if (*it != '*') {
return false;
}
}
return true;
}
}
//如果匹配字符串为空串
if (p.size() == 0) {
if (s.size() == 0) { //如果字符串为空,那么一定相等
return true;
} else { //字符串不为空,一定不等
return false;
}
}
int m = s.size();
int n = p.size();
bool matrix[m + 1][n + 1] = {{0}};
//初始化
//只需要初始化p[0]一行
matrix[0][0] = true;
//如果当前字符不是*,一定是错误的
for (int i = 1; i <= n; i++) {
if (p.at(i - 1) == '*') {
matrix[0][i] = matrix[0][i - 1];
} else {
matrix[0][i] = false;
}
}
//动态规划
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (s.at(i - 1) == p.at(j - 1) || p.at(j - 1) == '?') { //当前字符一定额能够匹配
matrix[i][j] = matrix[i - 1][j -1];
} else if (p.at(j - 1) == '*' && (matrix[i - 1][j - 1] || matrix[i][j - 1] || matrix[i - 1][j])) { //当前字符可能和前面的匹配
matrix[i][j] = true;
}
}
}
return matrix[m][n];
return matrix[m][n];
}
};