From 7cbf74913409c08f27650744cbea179575265756 Mon Sep 17 00:00:00 2001 From: ShusenTang Date: Sat, 1 Feb 2020 21:20:09 +0800 Subject: [PATCH] add 10 --- README.md | 1 + solutions/10. Regular Expression Matching.md | 112 +++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 solutions/10. Regular Expression Matching.md diff --git a/README.md b/README.md index 3918543..74d35ad 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ My LeetCode solutions with Chinese explanation. 我的LeetCode中文题解。 | 7 |[Reverse Integer](https://leetcode.com/problems/reverse-integer)|[C++](https://github.com/ShusenTang/LeetCode/blob/master/solutions/7.%20Reverse%20Integer.md)|Easy| | | 8 |[String to Integer (atoi)](https://leetcode.com/problems/string-to-integer-atoi)|[C++](https://github.com/ShusenTang/LeetCode/blob/master/solutions/8.%20String%20to%20Integer%20(atoi).md)|Medium| | | 9 |[Palindrome Number](https://leetcode.com/problems/palindrome-number)|[C++](https://github.com/ShusenTang/LeetCode/blob/master/solutions/9.%20Palindrome%20Number.md)|Easy| | +| 10 |[Regular Expression Matching](https://leetcode.com/problems/regular-expression-matching/)|[C++](solutions/10.%20Regular%20Expression%20Matching.md)|Hard| | | 11 |[Container With Most Water](https://leetcode.com/problems/container-with-most-water)|[C++](https://github.com/ShusenTang/LeetCode/blob/master/solutions/11.%20Container%20With%20Most%20Water.md)|Medium| | | 12 |[Integer to Roman](https://leetcode.com/problems/integer-to-roman)|[C++](https://github.com/ShusenTang/LeetCode/blob/master/solutions/12.%20Integer%20to%20Roman.md)|Medium| | | 13 |[Roman to Integer](https://leetcode.com/problems/roman-to-integer)|[C++](https://github.com/ShusenTang/LeetCode/blob/master/solutions/13.%20Roman%20to%20Integer.md)|Easy| | diff --git a/solutions/10. Regular Expression Matching.md b/solutions/10. Regular Expression Matching.md new file mode 100644 index 0000000..0b4846b --- /dev/null +++ b/solutions/10. Regular Expression Matching.md @@ -0,0 +1,112 @@ +# [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>dp(sn + 1, vector(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]; + } +}; +``` \ No newline at end of file