【LeetCode刷題記錄】10. 正則表達式匹配

題目描述:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
題解:
一、回溯
檢查時,如果只考慮’.’,顯然只需要同時在s和p中越過當前元素就可以了。
對於’*’,要分兩種情況考慮:1、表示0個前面那個元素,這時在p中越過它和它前面那個元素,s不變;2、表示1個前面那個元素(可以擴展到n個),這時在s中越過當前元素,p不變。
引用官方解答的一句話:“當模式串中有星號時,我們需要檢查匹配串 s 中的不同後綴,以判斷它們是否能匹配模式串剩餘的部分。一個直觀的解法就是用回溯的方法來體現這種關係。”
遞歸實現:

bool isMatch(string s, string p) {
 if (p.empty())return s.empty();
 if ('*' == p[1])
  return isMatch(s, p.substr(2)) || (!s.empty() && (s[0] == p[0] || '.' == p[0]) && isMatch(s.substr(1), p));
 else
  return !s.empty() && (s[0] == p[0] || '.' == p[0]) && (isMatch(s.substr(1), p.substr(1)));
}

複雜度分析:用T和P分別表示匹配串和模式串的長度,時間複雜度:O((T+P)2(T+P/2));空間複雜度:O((T+P)2(T+P/2))。
二、動態規劃
着重解釋DP,由簡單到複雜。
我們用dp[i][j]表示s的前i個能否被p的前j個匹配;首先,只考慮’.’,很容易得到這樣的狀態轉移方程:

if(s[i] == p[j] || '.' == p[j]) dp[i][j] = dp[i-1][j-1];

在此基礎上,考慮這個問題:

s = "abc";
p = "a.c";

首先,定義dp數組,顯然,因爲有初始狀態的存在,dp的大小應爲(s.size()+1)*(p.size()+1):
在這裏插入圖片描述
初始化dp[0][0],即s、p都取空,顯然dp[0][0] = true;初始化dp[0][j],1<=j<=3,顯然,均爲false;初始化dp[i][0],1<=i<=3,顯然,均爲false。
在這裏插入圖片描述
接着,應用狀態轉移方程,因爲i=0,j=0,s[0]==p[0],所以dp[1][1]=dp[0][0]=true,注意這裏dp數組的下標;
i=0,j=1,s[0]!=p[1],’.’==p[1],所以dp[1][2]=dp[0][1]=false;繼續循環,逐次填充dp數組。
在這裏插入圖片描述
注意:程序實現時,在定義dp數組時,所有元素初始化爲false。
令m=s.size(),n=p.size();顯然,dp[m][n]=dp[3][3]=true即爲結果。
將上述過程應用於:

s = "abc";
p = ".c";

得到:
在這裏插入圖片描述
顯然,dp[m][n]=dp[3][2]=false即爲結果。
只考慮’.’,代碼:

bool isMatchOnly(string s, string p) {
 if (p.empty()) return s.empty();
 int m = int(s.size());
 int n = int(p.size());
 vector<vector<bool>> dp(m + 1, vector<bool>(n + 1, false));
 dp[0][0] = true;
 for (int i = 0; i < m; i++) {
  for (int j = 0; j < n; j++) {
   if (s[i] == p[j] || '.' == p[j])
    dp[i + 1][j + 1] = dp[i][j];
  }
 }
 return dp[m][n];
}

以上爲只考慮’.‘的情況,應用動態規劃看起來有點多餘,但是一旦’*‘和’.'同時考慮,問題就複雜的多,動態規劃的意義就體現出來了。
下面開始同時考慮:

if(s[i] == p[j] || '.' == p[j]) dp[i][j] = dp[i-1][j-1]; //'.' 顯然,依舊成立。
if('*' == p[j]){ //'*'
 //case 1:'*'的上一個字符!=s[i]與'.',顯然這種情況下'*'只能代表0個上一個字符。

  //例如s="ab",p="ac*b",則有:
  //i=0,j=2時,檢查"a"能否被"ac*"匹配:
  //顯然,此時*代表0個上一個字符,這種情況下:dp[i][j] = dp[i][j-2];
  //即"a","ac*"->(狀態轉移至)"a","a";
  
  //狀態轉移方程:
 if(s[i] != p[j-1] && '.' != p[j-1]) dp[i][j] = dp[i][j-2]; 
 
 //case 2:'*'的上一個字符==s[i]或'.':共有3種情況:'*'代表0,1,多個上一個字符
 //以下三種情況都以s="abbbbbc",p="abb*bc"爲例分析:
 
  //case 2.1:i=1,j=3時,檢查"ab"能否被"abb*"匹配:
  //顯然,此時'*'代表0個上一個字符,這種情況下:dp[i][j] = dp[i][j-2];
  //即"ab","abb*"->(狀態轉移至)"ab","ab"
  
  //case 2.2:i=2,j=3時,檢查"abb"能否被"abb*"匹配:
  //顯然,此時'*'代表1個上一個字符,這種情況下:dp[i][j] = dp[i][j-1];
  //即"abb","abb*"->(狀態轉移至)"abb","abb"
  
  //case 2.3:i=3,j=3時,檢查"abbb"能否被"abb*"匹配:
  //顯然,此時'*'代表2(多)個上一個字符,這種情況下:dp[i][j] = dp[i-1][j];
  //即"abbb","abb*"->(狀態轉移至)"abb","abb*",
  
  //這裏觀察到,case 2.3轉移後的狀態,剛好是case 2.2轉移前的狀態,
  //再次應用case 2.3的結論,"abb","abb*"->(狀態轉移至)"ab","abb*"
  //又剛好是case 2.1轉移前的狀態!
  //這說明,如果'*'代表n個上一個字符,我們通過在待配串中減少末尾的一個字符(i-1),
  //問題就歸結(狀態轉移)爲'*'代表n=n-1個上一個字符,直至n=0,歸結爲"0"(case 1或case 2.1);
  //即,case 2.2是冗餘的!
  
  //注意:進入case 2的條件是:'*' == p[j] && (s[i] == p[j-1] || '.' == p[j-1])
  //那麼我們執行case 2.1,2.3中的哪一個呢?答案是:都執行,結果取或。
  //why?因爲'*'代表0個或多個前一個元素,
  //執行case 2.1就相當於我們去驗證“'*'代表0個前一個元素”這個命題對不對;
  //執行case 2.3就相當於我們去驗證“'*'代表多個前一個元素”這個命題對不對;
  //顯然,這兩個命題在dp的相同位置,有且僅有一個爲真(即'*'只能有一種含義)。
  //我們把這兩者的結果取或,即可保證真命題的結果的有效性(因爲另一個假命題的結果一定是false)。
  
  //狀態轉移方程:
 if(s[i] == p[j-1] || '.' == p[j-1]) 
  dp[i][j] = dp[i][j-2] /*|| dp[i][j-1]*/|| dp[i-1][j];
}

分析完畢,實戰:

s = "abc";
p = "a.*";

初始化dp[0][0],即s、p都取空,顯然dp[0][0] = true;初始化dp[0][j],1<=j<=3,顯然,均爲false;初始化dp[i][0],1<=i<=3,顯然,均爲false。
注意:假如s爲空,p="a*"及類似組合,那麼dp[0][j]會發生變化,需另做處理。
在這裏插入圖片描述
注意一點:p[0]不會爲星號,因爲沒意義,不需要考慮;即填寫dp[i][1]時,不會出現訪問越界的情況。
繼續填寫dp數組:
在這裏插入圖片描述
顯然,dp[m][n]=dp[3][3]=true即爲結果。
完整代碼:

bool isMatchDP(string s, string p) {
 if (p.empty()) return s.empty();
 int m = int(s.size()), n = int(p.size());
 vector<vector<bool>> dp(m + 1, vector<bool>(n + 1, false));
 dp[0][0] = true;
 //init: s="",p="a*"or".*"or...
 for (int j = 1; j < n; j++){
  if ('*' == p[j] && dp[0][j - 1] == true)
   dp[0][j + 1] = true;
 }
 for (int i = 0; i < m; i++) {
  for (int j = 0; j < n; j++) {
   if (s[i] == p[j] || '.' == p[j])
    dp[i + 1][j + 1] = dp[i][j];
   if ('*' == p[j]) {
    if (s[i] != p[j - 1] && '.' != p[j - 1])
     dp[i + 1][j + 1] = dp[i + 1][j - 1];
    else
     dp[i + 1][j + 1] = dp[i + 1][j - 1]/* || dp[i + 1][j] */|| dp[i][j + 1];
   }
  }
 }
 return dp[m][n];
}

複雜度分析:兩層循環,時間複雜度:O(mn);創建了二維dp數組,空間複雜度:O(mn)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章