题目描述
动态规划解法
1. dp数组的定义
设m为s的大小,n为p的大小,因为要考虑空串""
的匹配情况,所以dp数组应该是(m+1)*(n+1)
。dp数组形式如下:
dp数组 | “” | p[0] | p[1] | p[2] | … | p[j] | … | p[n] |
---|---|---|---|---|---|---|---|---|
“” | ||||||||
s[0] | ||||||||
s[1] | ||||||||
s[2] | ||||||||
… | ||||||||
s[i] | ||||||||
… | ||||||||
s[m] |
dp[i][j]
表示s[0...i-1]
与p[0...j-1]
是否能够匹配上,匹配为true
,不匹配就为fasle
。
2. 状态转移方程
dp[i][j]
的值为true还是false,取决于s[0...i-1]
与p[0...j-1]
能否匹配,就必须考虑当前正在遍历的s[i-1]
和p[j-1]
。
p[j-1]
有'a-z'
、'.'
、'*'
三种可能的值,前两种都容易分析,就是这个'*'
较为麻烦,它与前一个字符共同作用,而且可以匹配任意次数。所以我们以p[j-1]
是不是'*'
分情况讨论。
p[j-1] != *
1.1 p[j-1] == '.'
'.'
可以匹配任意字符,所以此时不管s[i-1]
为何值,s[i-1]
和p[j-1]
都是匹配的。那么这时s[0...i-1]
与p[0...j-1]
能否匹配,就取决于s[0...i-2]
与p[0...j-2]
,所以dp[i][j] = dp[i-1][j-1]
。
1.2 p[j-1] == 'a-z'
1.2.1 p[j-1] == s[i-1]
这种情况下,s[i-1]
和p[j-1]
都是匹配的。那么这时s[0...i-1]
与p[0...j-1]
能否匹配,就取决于s[0...i-2]
与p[0...j-2]
,所以dp[i][j] = dp[i-1][j-1]
。
1.2.2 p[j-1] != s[i-1]
这种情况下,s[i-1]
和p[j-1]
是不匹配的,所以dp[i][j] = fasle
。
p[j-1] == *
,此时主要看与*
搭配的字符,即p[j-2]
。
2.1 p[j-2] == 'a-z'
并且p[j-2]==s[i-1]
这种情况下,这对儿x*
是可以匹配的,要考虑匹配几次(0、1、多)。
如果匹配0次,那么s[0...i-1]
与p[0...j-1]
能否匹配,就取决于s[0...i-1]
与p[0...j-3]
能否匹配,所以dp[i][j] = dp[i][j-2]
。
如果要匹配大于0次,那么s继续往后走,p暂时不动。所以此时s[0...i-1]
与p[0...j-1]
能否匹配,就取决于s[0...i-2]
与p[0...j-1]
能否匹配,即dp[i][j] = dp[i-1][j]
。
所以最后,这种情况下,dp[i][j] = dp[i-1][j] || dp[i][j-2]
。
2.2 p[j-2] == 'a-z'
但是p[j-2]!=s[i-1]
这种情况下,*
只能匹配0次,即这对儿x*
作废,所以此时s[0...i-1]
与p[0...j-1]
能否匹配,就取决于s[0...i-1]
与p[0...j-3]
能否匹配,所以dp[i][j] = dp[i][j-2]
。
2.3 p[j-2] == '.'
即p[j-2]p[j-1]
为.*
。这种情况可以与2.1视为同一种情况,即dp[i][j] = dp[i-1][j]
。
3. dp数组初始化
-
空s
1.1 空p
s=""
和p=""
是可以匹配的,即空s可以匹配空p,因此dp[0][0]=true
。
1.2 非空p
s=""
和非空的p是有可能匹配的,即p="x*y*z*"
这种形式。所以,当1<=j<n
时,
1.2.1 若p[j-1] != '*'
,即p[j-1]
为'.'
或'a-z'
,此时是不匹配的,所以dp[0][j]=false
。
1.2.2 若p[j-1] == '*'
,因为*
可以匹配0次,所以这个*
可以和前边一个位置的字符消掉,即p[j-1]
和p[j-2]
不影响dp[0][j]
的值。所以这种情况下,dp[0][j]=dp[0][j-2]
。
- 空p
2.1 空s
同3.1.1
2.2 非空s
p=""
不可能和任何非空的s匹配成功,所以dp[i][0]=0
。
- 非空s与非空p
需要经过转移计算
初始化结果
经过上述步骤,dp数组应初始化为如下形式:
dp数组 | “” | p[0] | p[1] | p[2] | … | p[j] | … | p[n] |
---|---|---|---|---|---|---|---|---|
“” | 1 | 0 | 0或者dp[0][j-2] | 0或者dp[0][j-2] | 0或者dp[0][j-2] | 0或者dp[0][j-2] | 0或者dp[0][j-2] | 0或者dp[0][j-2] |
s[0] | 0 | |||||||
s[1] | 0 | |||||||
s[2] | 0 | |||||||
… | 0 | |||||||
s[i] | 0 | |||||||
… | 0 | |||||||
s[m] | 0 |
注:
dp[0][1]
一定为false。因为题目保证出现*
时,前边都有有效的字符与之匹配,即不可能是p[0]
一定不是*
。j>=2
时,dp[0][j]
的值需要按照3.1.2进行判断,有0或者dp[0][j-2]
两种可能。
4. 遍历顺序
显然是正序遍历
5. 返回形式
题目要求s[0...m-1]
与p[0...n-1]
能否匹配所以应该返回dp[m][n]
。
完整代码
bool isMatch(string s, string p) {
const int m = s.size();
const int n = p.size();
vector<vector<int>> dp(m+1, vector<int>(n+1, 0));
//初始化
dp[0][0] = 1;
for(int i = 1; i <= m; ++i){
dp[i][0] = 0;
}
for(int j = 1; j <= n; ++j){
p[j-1] == '*' ? dp[0][j] = dp[0][j-2] : dp[0][j] = 0;
}
//状态转移(遍历)
for(int i = 1; i <= m; ++i){
for(int j = 1; j <= n; ++j){
if(p[j-1] != '*'){
if(p[j-1] == s[i-1] || p[j-1] == '.'){
dp[i][j] = dp[i-1][j-1];
}
else{
dp[i][j] = false;
}
}
else{
if(p[j-2] == s[i-1] || p[j-2] == '.'){
dp[i][j] = dp[i-1][j] || dp[i][j-2];
}
else{
dp[i][j] = dp[i][j-2];
}
}
}
}
return dp[m][n];
}