1. 程式人生 > 實用技巧 >10. Regular Expression Matching

10. Regular Expression Matching

問題:

給定一個字串s,和一個模式串p,求p是否能匹配s

正則表達支援以下兩種符號

'.' Matches any single character.
'*' Matches zero or more of the preceding element.
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 preceding 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 = "c*a*b"
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 = "mis*is*p*."
Output: false

  

解法:DP(動態規劃)

1.確定【狀態】:

  • 字串s的第i個字元:s[i]
  • 匹配串第j個字元:p[j]

2.確定【選擇】:dp[i][j] 分3種情況

  • s[i] == p[j] 或者 p[j] == '.' :該字元匹配
    • 前一個子串狀態: =dp[i-1][j-1]
  • p[j] == '*':該字元可能匹配上"#*"(#為某一字元p[j-1]),分以下2種情況
    • p[j-1]這個字元不匹配當前s[i],即p[j-1]!=s[i] && p[j-1]!='.'
      • 則要使該字元匹配,"#*"必定與字串s匹配0次(匹配串的 j 跳過兩個字元#*):=dp[i][j-2]
    • p[j-1]這個字元匹配當前s[i],則又分為以下三種"#*"的匹配情況,他們之間求OR:
      • 匹配0次(匹配串的 j 跳過兩個字元#*,字串的 i 跳過0個字元):=dp[i][j-2]
      • 匹配1次(匹配串的 j 跳過兩個字元#*,字串的 i 跳過一個字元):=dp[i-1][j-2]
      • 匹配>1次(匹配串的 j 跳過0個字元,字串的 i 跳過一個字元):=dp[i-1][j]
  • 該字元未匹配
    • false

3. dp[i][j]的含義:

字串s的0~第 i 個字元,是否能被匹配串p的0~第 j 個字元,匹配上。

4. 狀態轉移:

dp[i][j]=

  • (s[i] == p[j] 或者 p[j] == '.'):=前一個子串狀態:dp[i-1][j-1]
  • (p[j] == '*'):
    • 前一個字元#不匹配s當前字元(p[j-1]!=s[i] && p[j-1]!='.')
      • 則使#*匹配0次:dp[i][j-2]
    • 前一個字元#匹配s當前字元,OR {
      • 使#*匹配0次:dp[i][j-2]
      • 使#*匹配1次:dp[i-1][j-2]
      • 使#*匹配>1次:dp[i-1][j] }
  • 其他則不匹配:=false

5. base case:

  • dp[i][0]=false:任意字串s,匹配空串,除非空串自己,其他都為false。
    • dp[0][0]=true
  • dp[0][2j]=true:當dp[0][2j-1]==true && p[2j]=='*'
    • "#*#*#*...#*"只有這種情況能匹配任意空串字串s。

程式碼參考:

 1 class Solution {
 2 public:
 3     //dp[i][j]:s[0~i],p[0~j] are matched?
 4     //case_1:s[i]==p[j] or p[j]=='.': dp[i-1][j-1]
 5     //case_2:p[j]=='*':
 6     //       case_2_1, s[i]!=p[j-1]->match 0 time: dp[i][j-2]
 7     //               #####a(i)
 8     //                    ^
 9     //               ####b*(j)
10     //                  ^
11     //       case_2_2, s[i]==p[j-1] or p[j]=='.' ->
12     //               #####a(i)   or   #####a(i)
13     //               ####.*(j)        ####a*(j)
14     //                 match 0 time: dp[i][j-2]
15     //              or match 1 time: dp[i-1][j-2]
16     //               #####a(i)
17     //                   ^
18     //               ####a*(j)
19     //                  ^
20     //              or match >1 times: dp[i-1][j]
21     //               #####a(i)
22     //                   ^
23     //               ####a*(j)
24     //                    ^
25     //base case:
26     //dp[i][0]=false
27     //dp[0][j]=p[j]=='*'&&dp[0][j-2]->"#*#*#*..."
28     //dp[0][0]=true
29     bool isMatch(string s, string p) {
30         int n=s.length(), m=p.length();
31         vector<vector<bool>> dp(n+1, vector<bool>(m+1, false));
32         dp[0][0]=true;
33         for(int j=2; j<=m; j+=2) {
34             if(p[j-1]=='*' && dp[0][j-2]) {
35                 dp[0][j] = true;
36             }
37         }
38         for(int i=1; i<=n; i++) {
39             for(int j=1; j<=m; j++) {
40                 if(s[i-1] == p[j-1] || p[j-1] == '.') {
41                     dp[i][j] = dp[i-1][j-1];
42                 } else if(p[j-1] == '*') {
43                     if(s[i-1] != p[j-2] && p[j-2] != '.') {
44                         dp[i][j] = dp[i][j-2];
45                     } else {
46                         dp[i][j] = dp[i][j-2] || dp[i-1][j-2] || dp[i-1][j];
47                     }
48                 }
49             }
50         }
51         return dp[n][m];
52     }
53 };