LeetCode——10. Regular Expression Matching

一.題目連接:https://leetcode.com/problems/regular-expression-matching/c++

二.題目大意:正則表達式

  實現一個正則表達式,該正則表達式只有兩種特殊的字符——「.」和「*」,其中.能表示任意字符,即它能夠匹配任意的字符;*表示能夠重複前面的字符0次或者屢次(例如:a*能夠表示成「」(空,至關於重複0次)或者表示成「aaa」重複3次)。express

三.題解:函數

  這道題目比較經常使用的方法是遞歸;該題目的難點之處在於遇到*的時候它可能匹配0次,也可能匹配1次或屢次;這種特性很顯然知足遞歸的特性。因此要根據字符是否爲*來進行分狀況討論,詳細以下:優化

對於一個模式字符串,從前日後的來分析它:spa

1.若是當前字符的下一個字符不是「*」的話(因爲第一個字符不多是* 因此根據下一個字符是否爲*來分狀況才更加的合理),此時只須要判斷需匹配字符串s的當前字符和模式字符串p的當前字符是否相等便可。若是*s == *p或者*p == '.' && *s != '\0'的話,那麼當前字符匹配成功,就能夠繼續往下匹配了。指針

2.若是當前字符的下一個字符是「*」的話,此時就須要匹配當前字符0次、1次或屢次了(而此處的重複次數能夠用遞歸法來實現,也能夠用迭代法來實現),直到不能匹配更多的字符。code

3.既然是遞歸,那麼必定·要有終止條件的,該問題的終止條件顯然是:當p到達終點時(即*p == '\0'時)若是s也到達終點了,那麼返回true;若是s沒到達終點,那麼返回false。經過這個條件咱們也能夠看出,字符串p的最終長度必定是與字符串s的長度同樣,纔可以匹配成功。(這也就是題目時所說的徹底匹配,而不是部分匹配。經過這一點,咱們可也快速的去除那些不符合匹配的例子,如:ab和.*c,二者顯然不匹配;而ab和.* 二者顯然匹配)blog

具體代碼以下:遞歸

方法1(遞歸+部分迭代):

class Solution {
public:
    bool isMatch(string s, string p) {
        return matchCore(s.c_str(),p.c_str());
    }
    bool matchCore(const char *s,const char *p)
    {
        if(*p == '\0' && *s == '\0')
            return true;
        if(*p == '\0' && *s != '\0')
            return false;
        //若是下一個字符不是*的話
        if(*(p+1) != '*')
        {
            if(*p == *s||(*p == '.' && *s != '\0'))
                return matchCore(s + 1,p + 1);
            else//一旦有一個字符不同,則不匹配
                return false;
        }
        else//下一個字符是*
        {
            //*重複一次或屢次,只有噹噹前字符匹配成功時,才考慮*重複多少次,不然不用考慮*了,直接跳過
            while(*p == *s ||(*p == '.' && *s != '\0'))
            {
                if(matchCore(s,p + 2))
                    return true;
                s++;
            }
            //*再也不重複,即直接跳過*
            return matchCore(s,p + 2);
        }

    }
};

注:

1.該方法中while循環的部分至關於對*進行匹配1次或屢次,而matchCore(s,p+2)至關於忽略* ,即匹配0次。下面來詳細闡述一下此處過程:

(1)首先,先判斷p和s的當前字符是否相同,若是不相同的話,* 這個字符就能夠忽略了,至關於匹配0次,即執行matchCore(s,p+2);

(2)若是p和s的當前字符相同的話,此時要考慮對*進行處理了,即匹配0次、1次仍是屢次?首先先嚐試匹配0次,若是匹配0次的話,p和s的剩下部分都相匹配的話,那麼直接就能夠返回true了,if(machCore(s,p+2) retrun true;這兩行代碼就是這個意思。若是匹配0次的話不可行怎麼辦?此時就是s++至關於匹配1次(若是if(matchCore(s,p+2)這個條件成立的話,至關於匹配1次就成功了),匹配一次完以後再進行判斷p和s的當前字符是否相同,而後重複以前的過程;這樣就完成了匹配0次、1次或屢次這整個過程。

2.必定要注意遞歸函數的終止條件!!

3.此處有個c++方法須要注意,即string.c_str()的用法,它的功能是將一個string類型的字符串轉換成一char *字符串。

方法2(所有遞歸):

class Solution {
public:
    bool isMatch(string s, string p) {
        return matchCore(s.c_str(),p.c_str());
    }
    bool matchCore(const char *s,const char *p)
    {
        if(*p == '\0' && *s == '\0')
            return true;
        if(*p == '\0' && *s != '\0')
            return false;
        //若是下一個字符不是*的話
        if(*(p+1) != '*')
        {
            if(*p == *s||(*p == '.' && *s != '\0'))
                return matchCore(s + 1,p + 1);
            else//一旦有一個字符不同,則不匹配
                return false;
        }
        else//下一個字符是*
        {

            if(*p == *s || (*p == '.' && *s != '\0'))//只有噹噹前字符匹配成功時,才考慮*重複多少次,不然不用考慮*了,直接跳過
                return matchCore(s,p + 2) || matchCore(s + 1,p + 2) || matchCore(s + 1,p); //*重複0次、一次或屢次
            else
                return matchCore(s, p + 2);
        }

    }
};

注:

所有遞歸的話,比以前的"遞歸+迭代"法滿了很多,方法1用了23ms,但方法2(本方法)用了731ms。因此能不用遞歸就不用遞歸,這句話是頗有道理的;但遞歸法寫的代碼確實看上去更加容易理解,雖然相比迭代有時會爆棧或超時(超時頗有多是添加了多餘的操做,有時這種多餘的操做並不那麼的明顯)

方法3(所有遞歸-優化):

class Solution {
public:
    bool isMatch(string s, string p) {
        return matchCore(s.c_str(),p.c_str());
    }
    bool matchCore(const char *s,const char *p)
    {
        if(*p == '\0' && *s == '\0')
            return true;
        if(*p == '\0' && *s != '\0')
            return false;
        //若是下一個字符不是*的話
        if(*(p+1) != '*')
        {
            if(*p == *s||(*p == '.' && *s != '\0'))
                return matchCore(s + 1,p + 1);
            else//一旦有一個字符不同,則不匹配
                return false;
        }
        else//下一個字符是*
        {

            if(*p == *s || (*p == '.' && *s != '\0'))//只有噹噹前字符匹配成功時,才考慮*重複多少次,不然不用考慮*了,直接跳過
                return matchCore(s,p + 2) || matchCore(s + 1,p); //*重複匹配s中字符0次、一次或屢次
            else
                return matchCore(s, p + 2);//若是當前字符不相同的話,把該字符和*一塊去掉(至關於重複0次),去匹配pattern+2後的部分
        }

    }
};

方法3相比方法2,主要是優化了一處,即

return matchCore(s,p + 2) || matchCore(s + 1,p + 2) || matchCore(s + 1,p);

變成了

return matchCore(s,p + 2) || matchCore(s + 1,p);

由於仔細想一想,重複屢次是由多個重複1次組成的啊,因此最終不用再強調重複一次了,直接用matchCore(s+1,p)既能夠表示重複1次也能夠表示成重複屢次。

那麼去除這一步後,耗時多少呢?是26ms!!可見遞歸併不比迭代慢,主要是是否不增長多餘的操做。話說,作到這一步內心非常挺爽的~~

此外,還有須要注意的一點:對於任何字符串處理的題目,首先都須要判斷該字符串是否爲nullptr(空指針)或者爲""的狀況,這種特例必定是要考慮的!!(對於char *類型的字符串,這兩種狀況都須要考慮;對於string類型的字符串,通常只考慮""的狀況)

相關文章
相關標籤/搜索