題目
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
分析
實現一個簡單的正則匹配,其中僅涉及.和*的正則,該題是劍指offer原題,但是劍指offer的實現方法效率不高,而且其中的一種情況其實是可以直接省略的,即當前字符與模式匹配並且模式下一個字符爲*時,書上說可以(1)選擇文本後移一位,模式後移兩位,進入下一狀態;(2)文本後移一位,模式保持不變,留在當前狀態;(3)文本不變,模式後移兩位,忽略當前模式。但其實情況(1)沒必要單獨列出,因爲情況(1)相當於情況(2)遞歸時選擇情況(3),並且從前向後匹配比從後向前匹配要慢,因此改爲從後向前匹配並且忽略情況(1)。官方給出另外一種遞歸方法實現比較簡單,但是由於頻繁調用string.substr()導致效率不高,另外動態規劃方法可以參考My concise recursive and DP solutions with full explanation in C++ 。
從後向前遞歸代碼:
class Solution {
public:
bool isMatch(string s,string p,int s_s,int p_s){
if(s_s==-1&&p_s==-1)//同時到達字符串前時,表示匹配完成
return true;
if(s_s>-1&&p_s==-1)//模式先到頭時,表示匹配未完成
return false;
if(p[p_s]=='*'){//模式中有*號時
if(s_s>-1&&(s[s_s]==p[p_s-1]||p[p_s-1]=='.'))//如果此時模式*前的字符與文本匹配或者模式*前字符爲.則可以選擇匹配成功但模式保持當前或者不匹配並且跳過當前模式,劍指offer中的匹配成功並且跳過當前模式,即當前模式僅匹配一次,實際上與匹配成功並且模式保持不變並且在下一次跳過當前模式對應,因此可以省略
return isMatch(s,p,s_s-1,p_s)||isMatch(s,p,s_s,p_s-2);
else
return isMatch(s,p,s_s,p_s-2);//如果模式*前字符與文本不匹配,則跳過當前模式
}
if(s_s>-1&&p_s>-1&&(s[s_s]==p[p_s]||p[p_s]=='.'))//如果模式此時僅爲字符或者.則向前移一個元素
return isMatch(s,p,s_s-1,p_s-1);
return false;
}
bool isMatch(string s, string p) {
return isMatch(s,p,s.length()-1,p.length()-1);//從後往前進行匹配
}
};
官方簡潔遞歸代碼:
class Solution {
public:
bool isMatch(string s, string p) {
if(p.empty())
return s.empty();
bool first_match=(!s.empty())&&(p[0]==s[0]||p[0]=='.');//判斷第一個元素是否能匹配
if(p.length()>=2&&p[1]=='*'){
return (isMatch(s,p.substr(2))||(first_match&&isMatch(s.substr(1),p)));//對應模式中有*時的情況,要麼模式跳過,要麼第一個元素匹配的話則模式保持
}
else{
return first_match&&isMatch(s.substr(1),p.substr(1));//如果第一個元素匹配則完成一次匹配
}
}
};