10.Regular Expression Matching
题目描述如下:
Given an input string (s) and a pattern §, 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).
Note:
s could be empty and contains only lowercase letters a-z.
p could be empty and contains only lowercase letters a-z, and characters like . or *
Example 1:
Input:
s = “aa”
p = “a”
Output: false
Explanation: “a” does not match the entire string “aa”.
Example 2:
Input:
s = “aa”
p = “a*”
Output: true
Explanation: '’ means zero or more of the precedeng element, ‘a’. Therefore, by repeating ‘a’ once, it becomes “aa”.
Example 3:
Input:
s = “ab”
p = "."
Output: true
Explanation: “." means "zero or more () of any character (.)”.
Example 4:
Input:
s = “aab”
p = “cab”
Output: true
Explanation: c can be repeated 0 times, a can be repeated 1 time. Therefore it matches “aab”.
Example 5:
Input:
s = “mississippi”
p = “misisp*.”
Output: false
这题是一个匹配正则表达式的简化版,除了普通字符直接匹配以外,有两种特殊字符,.匹配任意单个字符,而 *是作为它的前一个字符的修饰符,表示其前一个字符可以重复n次(n>=0),这题最朴素的做法就是在遇到 * 的时候尝试一个个可能重复的次数,然后一次次的去匹配,但是这样做的效率显然是很低的,因为这样的复杂度其实是以乘号的个数呈一个次方的规模向上增长的,比如第一个乘号可以代表重复01234次,而第二个也可以重复01234次,这样就已经有25次了,当乘号变多的时候需要的计算量就会爆炸增长,但是我们仔细思考后其实可以发现这道题可以用动态规划的方式去做,我们先假设dp(i,j)表示在字符串s的下标i之前的部分和字符串p的下标j之前的部分是匹配的,那么我们考虑p[j]的情况:
1.如果p[j]不是乘号,那么就说明s[i]和p[j]至少是相等的,否则不可能匹配成功,而且与此同时除了这一位之外前面的那几位都是匹配的,也就是dp(i-1,j-1)为真
2.如果p[j]是乘号,那这个乘号代表前一个元素的重复,重复个数可能为0或不为0,如果为0,那么就可以直接忽略乘号前面的那个元素,也就是dp(i,j) = dp(i,j-2);而如果重复个数不为0,那么就说明s[i] 匹配 p[j-1],而且这个乘号还可以继续往下匹配,也就是dp(i,j) = (s[i] 匹配 p[j-1]) && dp(i-1,j)
这样我们就找到了一个很好的状态转移方程,我们要算的其实就是dp(s.size()-1,p.size()-1),而要算出这个值其实并没有必要算出前面的每个值,按需计算即可,如果有需要的值再递归下去计算,这样进行dp的次数最多就为SP,S,P分别为s和p的长度,而且因为并没有算出所有值,所以实际的调用次数还会更小,具体代码实现如下:
class Solution
{
public:
bool isMatch(string s, string p)
{
str = s;
pattern = p;
bool ans;
result = new int[str.size() * pattern.size()];
for(int i = 0; i < str.size() * pattern.size(); i++)
{
result[i] = 0;
}
ans = dp(str.size() - 1, pattern.size() - 1);
delete []result;
return ans;
}
private:
bool dp(int i, int j)
{
bool ans;
if(i < 0){
if( j < 0)
return true;
else if(pattern[j] == '*'){
return dp(i,j - 2);
}
else return false;
}
else if(j < 0)
{
return false;
}
else
{
if(result[i * pattern.size() + j])
{
ans = result[i * pattern.size() + j] == 2;
return ans;
}
if(pattern[j] != '*')
{
ans = matchSingle(str[i], pattern[j]) && dp(i - 1, j - 1);
result[i * pattern.size() + j] = ans ? 2 : 1;
return ans;
}
else
{
ans = dp(i, j - 2) || (dp(i - 1, j) && matchSingle(str[i], pattern[j - 1]));
result[i * pattern.size() + j] = ans ? 2 : 1;
return ans;
}
}
}
bool matchSingle(char s, char p)
{
return p == '.' || s == p;
}
string str;
string pattern;
int *result;
};
复杂度正如以上分析为O(SP),因为重复的值不会被再次计算,而且每个dp除了递归外都没有循环,复杂度为O(1),而且因为是按需计算所以实际的计算费用会更少,实测上解超过leetcode提交100%的运行效率。