一. Regular Expression Matching
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”) → false
isMatch(“aa”,”aa”) → true
isMatch(“aaa”,”aa”) → false
isMatch(“aa”, “a*”) → true
isMatch(“aa”, “.*”) → true
isMatch(“ab”, “.*”) → true
isMatch(“aab”, “c*a*b”) → true
Difficulty:Hard
TIME:TIMEOUT
解法一(深度优先搜索)
这道题主要是要理解匹配的过程,就是要把匹配的过程分成几种情况分别讨论,最简单的做法当然是用递归来求解,以下就对递归过程做一个描述。
首先是有*号的情况,也就是匹配0次或者1次以上:
- 如果匹配0次,则isMatch(s,p) = isMatch(s,p.substr(2))
- 如果匹配一次以上,则isMatch(s,p) = isMatch(s.substr(1),p)
然后是没有*号的情况,也就是只匹配一次:
- 则isMatch(s,p) = isMatch(s.substr(1),p.substr(1))
void dfs(string s, int ss, string p, int pp, bool &result) {
if (pp >= p.size() && ss >= s.size())
result = true;
if (result || pp >= p.size())
return;
if (pp + 1 < p.size() && p[pp + 1] == '*') { //有*的情况
dfs(s, ss, p, pp + 2, result); //必定可以跳过
if ((p[pp] == '.' || p[pp] == s[ss]) && ss + 1 <= s.size())
dfs(s, ss + 1, p, pp, result); //但不一定能匹配一次以上
}
else if ((p[pp] == '.' || p[pp] == s[ss]) && ss + 1 <= s.size())
dfs(s, ss + 1, p, pp + 1, result); //没有*的情况
}
bool isMatch(string s, string p) {
bool result = false;
dfs(s, 0, p, 0, result);
return result;
}
代码的时间复杂度应该约为 O(2n) 。
解法二(动态规划)
一开始压根没有想到这道题能用动态规划来解,但确实可以这样,因为这道确实存在着最优子结构,而且和之前做过的Distinct Subsequences十分相似。
首先使用一个二维数组dp,第一维表示字符串s的长度,而第二维表示字符串p的长度(注意是长度而不是下标),值为true或false,也就是p的前缀能否匹配s的前缀。
为什么要采用长度而不是下标呢,首先一点处理空串会简单很多(如果采用下标,空串要单独处理),第二点是可以避免很多的边界条件判断(之前要处理下标是-1的状态,现在只需要处理下标是0的状态就行了)。因此代码会简洁不是一点,Distinct Subsequences的处理方式也是同样的道理 。
首先处理空串的情况,很容易理解dp[0][0]为true,然后就是dp[0][j]的情况,也容易想到可以一直匹配成功直到出现第一个没有带*号的字符。而具体的最优子结构我直接用LeetCode上面的其他人写的作为参照,将会在代码中给出:
/**
* f[i][j]: if s[0..i-1] matches p[0..j-1]
* if p[j - 1] != '*'
* f[i][j] = f[i - 1][j - 1] && s[i - 1] == p[j - 1]
* if p[j - 1] == '*', denote p[j - 2] with x
* f[i][j] is true iff any of the following is true
* 1) "x*" repeats 0 time and matches empty: f[i][j - 2]
* 2) "x*" repeats >= 1 times and matches "x*x": s[i - 1] == x && f[i - 1][j]
* '.' matches any single character
*/
bool isMatch(string s, string p) {
vector<vector<bool>> dp(s.size() + 1, vector<bool>(p.size() + 1, false));
dp[0][0] = true;
for (int i = 1; i <= p.size(); i++) { //先处理空串的情况
if (p[i - 1] == '*' && dp[0][i - 2])
dp[0][i] = true;
}
for (int i = 1; i <= s.size(); i++) {
for (int j = 1; j <= p.size(); j++) {
if (j < p.size() && p[j] == '*') {
if(dp[i][j - 1] || ((s[i - 1] == p[j - 1] || p[j - 1] == '.') && dp[i - 1][j + 1]))
dp[i][j + 1] = true;
}
else if (dp[i - 1][j - 1] && (s[i - 1] == p[j - 1] || p[j - 1] == '.')) {
dp[i][j] = true;
}
}
}
return dp[s.size()][p.size()];
}
不得不说这个最优子结构是十分精妙的,很少的判断,很少的步骤,就能涵盖所有的情况。
代码的时间复杂度为 O(n2) 。