Leetcode OJ: Regular Expression Matching

Implement regular expression matching with support for '.' and '*'.express

'.' 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

實現個簡單的正則匹配,支持'.'與'*'的操做優化

解決這題的關鍵是思路要清晰,考慮要周全。spa

1. 遞歸實現。prototype

思路:指針

isMatch(s, p):code

1. 當前p爲0時,若s也是0時,則返回true,不然爲falseblog

2. 當前p不爲0時,遞歸

  1) p的下一個不是'*'時element

    if: 當前s與p匹配,leetcode

      則代表到此都是匹配的,只須要檢查isMatch(s + 1, p + 1)

    else:

      返回false

  2) p的下一個是'*'時,

    while: 當前s與p匹配,即代表至此也是匹配的

      if: 當前s與下一個p也都匹配,即isMatch(s, p + 2),則返回true

      else: s++

    此時,當前s與當前p已經不匹配了(以前s都是匹配的),則檢測下一個模板isMatch(s, p + 2)

代碼以下:

 1 class Solution {
 2 public: 
 3     bool isMatch(const char *s, const char *p) {
 4         if (*p == '\0') return *s == '\0';
 5         if (*(p+1) != '*') {
 6             return ((*p == *s) || (*p == '.' && *s != '\0')) && isMatch(s+1, p+1);
 7         }
 8         while ((*p == *s) || (*p == '.' && *s != '\0')) {
 9             if (isMatch(s, p+2)) return true;
10             s++;
11         }
12         return isMatch(s, p+2);
13     }
14 };

最終是116ms過了

2. 動態規劃實現

其實根據以上思路,就很容易就有動態規劃的思路了,小小的trick就是假設s與p的開頭都加了一個空字符,方便統一規則。

DP(i, j)存的是p[0:i-1]與s[0:j-1]的匹配狀況。

且看下面的圖與說明

DP(i+1, j+1)是要更新的狀態,對應p[i]和s[j]

1. 當p[i] 的下一個不爲'*'時,此時只跟上一個狀態的p與上一個s對應的DP(i, j)有關

 由於此時匹配的條件是:A. p[i]與s[j]匹配。B. p[0:i-1]與s[0:j-1]徹底匹配,即DP(i, j)==true。

 則有 

    DP(i+1, j+1) = DP(i, j) && Match(p[i], s[j])

 另外考慮DP(i+1, 0),這個值對應的是s[-1]與p[i],s[-1]即假設存在的空字符,明顯p[i]與s[-1]是不匹配的

   所以這時的DP(i+1, 0) = false

 而要讓規劃順利地開始,(特別是當第一個匹配對的就是這種狀況時)就須要讓DP(0, 0)=true了。

2. 當p[i]的下一個爲'*'時,此時就與圍繞這一狀態的都有關。

  1) 若是DP(i, j+1)是true時,即上一個pattern就已經徹底匹配了當前的s,那當前狀態也應該是true,即DP(i+1, j+1)=true

  2) 不然,DP(i, j)與DP(i+1, j)其中有一個爲true, 並且p[i]與s[j]匹配,則DP(i+1, j+1)爲true,即

    DP(i+1, j+1) = (DP(i, j) || DP(i+1, j)) && Match(p[i], s[j])

 此時考慮DP(i+1, 0)則須要DP(i, 0)的配合,DP(i+1, 0) = DP(i, 0) && Match(p[i], s[-1])

 而Match(p[i], s[-1])永遠爲真,因此DP(i+1, 0) = DP(i, 0)

另外考慮到匹配狀況只跟相鄰兩層有關,因此實現時就只用了兩層存狀態。代碼以下

 1 class Solution {
 2 public: 
 3     bool isMatchSingle(char s, char p) {
 4         return p == s || p == '.';
 5     }
 6     
 7     bool isMatch(const char *s, const char *p) {
 8         int slen = strlen(s);
 9         int plen = strlen(p);
10         // 用於存當前層匹配結果與上一層匹配結果
11         vector<bool> dp1(slen + 1, false), dp2(slen + 1, false);
12         vector<bool> *pre = &dp1, *cur = &dp2;
13         
14         // 第一個爲true,由於默認地給s與p的開始都添加了個空字符
15         dp1[0] = true;
16         const char* pp = p;
17         while (pp[0] != 0) {
18             // 指向當前層匹配結果與上一層匹配結果的引用
19             vector<bool>& curr = *cur;
20             vector<bool>& prer = *pre;
21             
22             // 初始化
23             curr.assign(slen + 1, false);
24             if (pp[1] != '*') {
25                 // curr[0]都將爲false
26                 for (int i = 0; i < slen; ++i) {
27                     curr[i + 1] = (prer[i] && isMatchSingle(s[i], pp[0]));
28                 }
29                 pp += 1;
30             } else {
31                 // curr[0]只與prer[0]相關
32                 curr[0] = prer[0];
33                 for (int i = 0; i < slen; ++i) {
34                     curr[i + 1] = (prer[i + 1] || (isMatchSingle(s[i], pp[0]) && (prer[i] || curr[i])));
35                 }
36                 pp += 2;
37             }
38             // 交換,注意是指針的交換,沒有換數據
39             swap(cur, pre);
40         }
41         return (*pre)[slen];
42     }
43 };

最終52ms過了。

我的也加了些優化,給每一層都加個計數器,當是沒有*的狀況下,上一層爲True的節點已經到結尾了,26行的循環就能夠跳出了。

實現了一記,24ms過了,有興趣的同窗能夠試試看。

這道題仍是挺複雜了,理清思路很重要。
相關文章
相關標籤/搜索