LeetCode/solutions/10. Regular Expression Matching.md
ShusenTang 7cbf749134 add 10
2020-02-01 21:20:09 +08:00

112 lines
4.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# [10. Regular Expression Matching](https://leetcode.com/problems/regular-expression-matching/)
# 思路
仅包含'.'和'*'以及小写字母的正则表达式匹配。模式中的字符
* '.'表示任意一个字符;
* 而'*'表示它前面的字符可以出现任意次包含0次
## 思路一、递归
递归解法先考虑如何匹配s中的第一个字符再递归匹配剩余部分。
由于模式的第二个字符可能是'*',所以模式的第一个字符不一定要匹配,例如模式'a\*bc'可以匹配'bc',所以我们按照模式中 **第二个字符是否为'\*'** 对情况分类:
* 若是,即`p[1] == '*'`,因为可以匹配第一个字符`p[0]`0次或>0次所以有两条路可尝试
* (1) 尝试匹配0次此时递归考虑模式p的剩余部分是否与s匹配即可即若`isMatch(s, p.substr(2))==true`则返回true程序结束否则进入(2)。
* (2) 尝试匹配>0次此时前提是p和s的第一个字符匹配成功即`(s[0] == p[0] || '.' == p[0])`然后递归考虑模式p是否与s的剩余部分是否匹配即可即若`isMatch(s.substr(1), p)==true`则返回true程序结束。若(1)(2)都不成立则匹配失败返回false程序结束。
* 若不是那就比较简单了直接判断模式p和s的第一个字符是否匹配然后再递归匹配剩余即可即返回`(s[0] == p[0] || '.' == p[0]) && isMatch(s.substr(1), p.substr(1))`。
另外还有一个重要的点就是递归出口,先来看三个边界情况:
|s|p|匹配结果|
| :-: | :-: | :-: |
|空|空|true|
|不空|空|false|
|空|不空|无法判断, 例若s为空, 则p='x*'时匹配成功, p='x'时匹配失败|
由上表可知递归出口为
``` C++
if(p.empty()) return s.empty();
```
所以s为空但p不为空时不会立即返回所以在访问`s[0]`之前需要先判断s是否为空见代码。
这种方法由于递归调用次数比较多,所以亲测比较慢。下面思路二采用动态规划迭代的方式要快很多。
## 思路二、动归
也可以使用动态规划的方法,因为匹配问题可以分解为单个字符进行匹配的子问题。
我们定义状态:
```
如果 s[0, 1, ..., i-1] 与 p[0, 1, ..., j-1] 匹配则 dp[i][j] = true 否则 dp[i][j] = false.
```
所以初始状态为`dp[0][0] = true`表示两个空串相匹配其他全部初始为false。
根据思路一的分析,我们有类似的状态转移方程:
* 如果模式最后一个字符(即`p[j-1]`)为'*',则有两条路:
* (1)尝试匹配0次即`p[j-2]`和`p[j-1]`都没用,所以如果`dp[i][j-2] == true`则`dp[i][j]`为true
* (2)尝试匹配1次即在前一个模式字符`p[j-2]`与待匹配字符`s[i-1]`相匹配的情况下,如果`dp[i-1][j]=true`则`dp[i][j]`为true
* 否则,即模式最后一个字符不为'*',则只能检查这个字符是否与待匹配字符是否匹配了,若匹配,且`dp[i-1][j-1]=true`则`dp[i][j]`为true。
与思路一类似由于s为空但p不为空时可能成功匹配所以第一层循环i应该从0开始而不是从1开始见代码。
# C++
## 思路一
``` C++
class Solution {
public:
bool isMatch(string s, string p) {
if(p.empty()) return s.empty();
#define MATCH (s[0] == p[0] || '.' == p[0])
// p的第二个字符是*
if(p[1] == '*'){ // 不会溢出, 因为p[p.size()] == '\0'
// 匹配0个字符, 此时若s为空也可能匹配成功
if(isMatch(s, p.substr(2))) return true;
// 匹配1个字符
if(s.size() && MATCH && isMatch(s.substr(1), p)) return true;
return false;
}
// p的第二个字符不是*
return s.size() && MATCH && isMatch(s.substr(1), p.substr(1));
}
};
```
## 思路二
``` C++
class Solution {
public:
bool isMatch(string s, string p) {
// if(p.empty()) return s.empty();
int sn = s.size(), pn = p.size();
vector<vector<bool>>dp(sn + 1, vector<bool>(pn + 1, false));
dp[0][0] = true;
for(int i = 0; i <= sn; i++){ // i = 0代表s为空的情况, 所以不能从1开始
for(int j = 1; j <= pn; j++){
if(j > 1 && p[j - 1] == '*'){ // 最后一个字符为*
// 匹配0个字符
if(dp[i][j-2]) dp[i][j] = true;
// 匹配1个字符
else if(i > 0 && (s[i - 1] == p[j - 2] || '.' == p[j - 2]) && dp[i-1][j])
dp[i][j] = true;
}
// 最后一个字符不是*
else if(i > 0 && (s[i - 1] == p[j - 1] || '.' == p[j - 1]) && dp[i-1][j-1])
dp[i][j] = true;
}
}
return dp[sn][pn];
}
};
```