[Leetcode] 10. Regular Expression Matching 解题报告
来源:互联网 发布:朱梓骁郭敬明 知乎 编辑:程序博客网 时间:2024/06/06 01:48
题目:
Implement regular expression matching with support for '.'
and '*'
.
'.' Matches any single character.'*' Matches zero or more of the preceding element.The matching should cover the entire input string (not partial).The function prototype should be:bool isMatch(const char *s, const char *p)Some examples:isMatch("aa","a") → falseisMatch("aa","aa") → trueisMatch("aaa","aa") → falseisMatch("aa", "a*") → trueisMatch("aa", ".*") → trueisMatch("ab", ".*") → trueisMatch("aab", "c*a*b") → true
思路:
这是一道相当难的题目,可以体现动态规划和回溯法的精髓。其中最难的部分是对‘*’的处理。
1、动态规划:定义dp[i][j]表示原字符串s的前i个字符和模式字符串p的前j个字符是否可以匹配。则递推式可以分为如下三种情况:
1)p[j] == '.' || p[j] == s[i]:此时dp[i+1][j+1] = dp[i][j],因为s和p的最后一个字符是匹配的。
2)p[j] == '*':此时p[j-1]可以选择在模式匹配中出现任意次,可以分为两种情况处理:
a)无论p[j-1]和s[i]是否匹配,p[j-1]都可以选择在模式匹配时出现0次。此时一旦dp[i+1][j-1] == true,则dp[i+1][j+1]为true。
b)如果p[j-1]和s[i]匹配,那么此时p[j-1]还可以选择在模式匹配时出现至少1次,可以分为两种情况:
情况1:p[j-1] 在模式匹配时出现1次,此时dp[i+1][j+1] = dp[i+1][j];
情况2:p[j-1]在模式匹配时出现多于1次,此时由于p[j-1]和s[i-1]至少匹配1次,故有dp[i+1][j+1] = dp[i][j+1]。
需要注意空串和空串能够匹配,即dp[0][0] = true,并基于该基础递推地更新dp[0][j] (0 < j <= n)。
由以上分析可知,dp[i+1][j+1]的计算依赖于dp[i][j], dp[i+1][j], dp[i][j+1], dp[i+1][j-1],因此i和j都可以按照递增次序计算,时间复杂度为O(m*n),其中m和n分别是s和p的长度。注意到dp[i+1][j+1]的计算仅仅依赖于其上一行的dp[i][j]和dp[i][j+1],以及本行的dp[i+1][j-1]和dp[i+1][j],所以我感觉理论上来讲可以将空间复杂度进一步优化到O(n),但是我目前实现的算法还是有问题,随后有空再debug(欢迎有兴趣的同学留言讨论)。目前AC代码中的空间复杂度依然为O(n*m)。
2、回溯法:其基本思路和动态规划基本一致,但是采用递归来实现。通过观察代码可知,在p[j] == '*'的时候,代码存在回溯的情况,因此一些中间状态有可能被重复计算。可是实际情况是在该测试用例中,回溯法竟然比动态规划要快很多!作者推测可能对于本题而言,重复计算所增加的代价要小于冗余计算所增加的代价(在该题的动态规划中,由于dp[i+1][[j+1]的状态来源不一,具体取决于s[i]和p[j]是否匹配以及p[j]是否为‘*’,所以动态规划中某些中间状态的计算是不必要的)。
为了进一步比较回溯法和动态规划法的性能,作者在回溯法的基础上记录状态(回溯法+记忆),以免重复计算中间状态。结果惊奇地发现,无论在回溯法中是否增加记忆,在Leetcode上的运行时间都是6ms!但是作者认为至少可以证明回溯法+记忆的策略要明显好于动态规划,因为在相同的空间复杂度下,前者的运行时间大大减少,说明回溯法+记忆确实有效避免了动态规划中不必要的中间状态计算。进一步验证发现,采用单纯回溯法,中间状态确实有被重复计算的情况,不过重复次数并不过多。所以结论是:是否增加记忆就是一个空间和时间之间的tradeoff了。
代码:
1、动态规划:
class Solution {public: bool isMatch(string s, string p) { if(s.length() == 0 && p.length() == 0) return true; vector<vector<bool>> dp(s.length() + 1, vector<bool>(p.length() + 1, false)); dp[0][0] = true; for(int j = 0; j < p.length(); ++j) // for the case "c*" dp[0][j + 1] = (p[j] == '*' && dp[0][j - 1]); for(int i = 0; i < s.length(); ++i) { for(int j = 0; j < p.length(); ++j) { if(p[j] == '.' || p[j] == s[i]) { dp[i + 1][j + 1] = dp[i][j]; } else if(p[j] == '*') { if(dp[i + 1][j - 1]) // match 0 time is OK { dp[i + 1][j + 1] = true; continue; } if(p[j-1] == s[i] || p[j-1] == '.') // can match at least 1 time { // dp[i+1][j] means match 1 time, dp[i][j+1] means match more than 1 time dp[i + 1][j + 1] = (dp[i + 1][j] || dp[i][j + 1]); } } } } return dp[s.length()][p.length()]; }};
2、回溯法(无状态记忆):
class Solution {public: bool isMatch(string s, string p) { int m = s.length(), n = p.length(); return backtracking(s, m, p, n); }private: bool backtracking(string &s, int i, string &p, int j) { // base case if(i == 0 && j == 0) return true; if(i != 0 && j == 0) return false; // deduction case if(i == 0 && j != 0) // s is finished, but p is not finished { if(p[j - 1] == '*') // only p == "c*c*c*" pattern can match null string return backtracking(s, i, p, j-2); return false; } if(s[i-1] == p[j-1] || p[j-1] == '.') { return backtracking(s, i-1, p, j-1); } else if(p[j-1] == '*') { if(backtracking(s, i, p, j-2)) // p[j-2]* matches zero characters of s return true; if(p[j-2] == s[i-1] || p[j-2] == '.') // p[j-2]* matches at least one time return backtracking(s, i-1, p, j); else return false; } else { return false; } }};
3、回溯法(有状态记忆):
class Solution {public: bool isMatch(string s, string p) { int m = s.length(), n = p.length(); states.resize(m + 1, vector<int>(n + 1, -1)); return backtracking(s, m, p, n) == 1; }private: bool backtracking(string &s, int i, string &p, int j) { if(states[i][j] >= 0) return states[i][j]; // base case if(i == 0 && j == 0) return states[i][j] = 1; else if(i != 0 && j == 0) return states[i][j] = 0; // deduction case if(i == 0 && j != 0) // s is finished, but p is not finished { if(p[j - 1] == '*') // only p == "c*c*c*" pattern can match null string return states[i][j] = backtracking(s, i, p, j-2); return states[i][j] = 0; } if(s[i-1] == p[j-1] || p[j-1] == '.') { return states[i][j] = backtracking(s, i-1, p, j-1); } else if(p[j-1] == '*') { if(backtracking(s, i, p, j-2)) // p[j-2]* matches zero characters of s return states[i][j] = 1; if(p[j-2] == s[i-1] || p[j-2] == '.') // p[j-2]* matches at least one time return states[i][j] = backtracking(s, i-1, p, j); else return states[i][j] = 0; } else { return states[i][j] = false; } } vector<vector<int>> states; // we may also use hash map instead};
- [leetcode] 10. Regular Expression Matching 解题报告
- [Leetcode] 10. Regular Expression Matching 解题报告
- [LeetCode] Regular Expression Matching 解题报告
- [LeetCode 解题报告]010.Regular Expression Matching
- (待解决)LeetCode 10. Regular Expression Matching 解题报告
- 【LeetCode】10.Regular Expression Matching(hard)解题报告
- leetcode解题笔记:Regular Expression Matching
- [LeetCode]10.Regular Expression Matching
- LeetCode --- 10. Regular Expression Matching
- [Leetcode] 10. Regular Expression Matching
- [LeetCode]10.Regular Expression Matching
- [leetcode] 10.Regular Expression Matching
- Leetcode-10.Regular Expression Matching
- leetcode 10. Regular Expression Matching
- leetcode 10. Regular Expression Matching
- Leetcode 10. Regular Expression Matching
- leetcode 10. Regular Expression Matching
- Leetcode 10. Regular Expression Matching
- 关于一个变态的异常
- 欢迎使用CSDN-markdown编辑器
- FastDFS使用经验分享
- 历届试题 核桃的数量
- Deep Learning 学习笔记 5:深度学习的训练过程 2
- [Leetcode] 10. Regular Expression Matching 解题报告
- Android开发 百度地图开发(定位、传感器应用)
- 给予U3D的增强现实实现思路(移动端扫描图片呈现模型并移动)
- 视频会议及流媒体十大开源项目
- Python语言编程(一)
- C++编程基础(1)
- Caffe中学习率策略应如何选择
- freeline集成到Android Studio
- eclipse中使用maven创建Java web项目的详细步骤