正則表達式匹配(動態迴歸系列)

一、題目描述

題目來源:https://leetcode-cn.com/problems/regular-expression-matching

給你一個字符串 s 和一個字符規律 p,請你來實現一個支持 ‘.’ 和 ‘*’ 的正則表達式匹配。

'.' 匹配任意單個字符
'*' 匹配零個或多個前面的那一個元素
所謂匹配,是要涵蓋 整個 字符串 s的,而不是部分字符串。

說明:

s 可能爲空,且只包含從 a-z 的小寫字母。
p 可能爲空,且只包含從 a-z 的小寫字母,以及字符 . 和 *。

示例 1:

輸入:
s = "aa"
p = "a"
輸出: false
解釋: "a" 無法匹配 "aa" 整個字符串。

示例 2:

輸入:
s = "aa"
p = "a*"
輸出: true
解釋: 因爲 '*' 代表可以匹配零個或多個前面的那一個元素, 在這裏前面的元素就是 'a'。因此,字符串 "aa" 可被視爲 'a' 重複了一次。

示例 3:

輸入:
s = "ab"
p = ".*"
輸出: true
解釋: ".*" 表示可匹配零個或多個('*')任意字符('.')。

示例 4:

輸入:
s = "aab"
p = "c*a*b"
輸出: true
解釋: 因爲 '*' 表示零個或多個,這裏 'c' 爲 0 個, 'a' 被重複一次。因此可以匹配字符串 "aab"。

示例 5:

輸入:
s = "mississippi"
p = "mis*is*p*."
輸出: false

二、AC方法

1. 回溯(遞歸解法)

public static boolean isMatch(String s, String p) {
        if (p.isEmpty()) {
            return s.isEmpty();
        }
        boolean firstMatch = (!s.isEmpty() && (p.charAt(0) == s.charAt(0) || p.charAt(0) == '.'));
        //一種是匹配0個,那麼只需要跳過p中的這兩個字符,繼續與s中的字符進行比較即可,
        // 如果是匹配多個(那麼第一個必須也匹配),那麼將s中的遊標往後移動一個,繼續進行判斷,這兩個條件只要其中一個能滿足即可。
        if (p.length() > 1 && p.charAt(1) == '*') {
            //匹配0個,則只需要跳過p,p移動2位,s保持不變(不需要firstMatch)
            //匹配多個,則p保持不變,p的光標還是保持在0位,s移動下一位(同時要求第一位也必須匹配即firstMatch)
            return (isMatch(s, p.substring(2)) || firstMatch && isMatch(s.substring(1), p));
        } else {
            //如果不是"*",則要求當前的第一個字符必須匹配,然後在下一個字符匹配
            return firstMatch && isMatch(s.substring(1), p.substring(1));
        }
    }

2. 自頂向下

參考:https://www.imooc.com/article/281353?block_id=tuijian_wz

2.1 思路過程

假定使用符號:
s[i:] 表示字符串s中從第i個字符到最後一個字符組成的子串
p[j:] 表示模式串p中,從第j個字符到最後一個字符組成的子串
match(i,j) 表示s[i:]與p[j:]的匹配情況,如果能匹配,則置爲true,否則置爲false。這就是各個子問題的狀態。
那麼對於match(i,j)的值,取決於p[j + 1]是否爲’*’。

curMatch = i < s.length() && s[i] == p[j] || p[j] ==.;
p[j + 1] !=*’,match(i,j) = curMatch && match(i + 1, j + 1)
p[j + 1] ==*’,match(i,j) = match(i, j + 2) || curMatch && match(i + 1, j)

以s = “aab”; p = "cab"爲例,先構建一個二維狀態空間來存儲中間計算得出的狀態值。橫向的值代表i,縱向的值代表j,match(0,0)的值即問題的解,用f代表false,t代表true

2.2 推算過程

求match(0,0): i = 0; j = 0; curMatch = false;

p[1] == * -> match(0,0) = match(0,2) || false && match(1,0)

轉化爲求子問題match(0,2)和match(1,0)

求match(0,2): i = 0; j = 2; curMatch = true;

p[1] == * -> match(0,2) = match(0,4) || true && match(1,2)

求match(0,4): i = 0; j = 4; curMatch = false;

j + 1 == 5 >= p.length() -> match(0,4) = curMatch = false;

match(0,4) = false;

回溯到第五步,求match(1,2): i = 1; j = 2; curMatch = true;

p[3] == * -> match(1,2) = match(1,4) || true && match(2,2)

求match(1,4): i = 1; j = 4; curMatch = false;

j + 1 == 5 >= p.length() -> match(1,4) = curMatch = false;

match(1,4) = false;

回溯到第10步,求match(2,2): i = 2; j = 2; curMatch = false;

p[3] == * -> match(2,2) = match(2,4) || false && match(3,2)

求match(2,4): i = 2; j = 4; curMatch = true;

j + 1 == 5 >= p.length() -> match(2,4) = curMatch = true;

match(2,4) = true;

回溯到第15步。

match(2,2) = true;

回溯到第10步。

match(1,2) = true;

回溯到第5步。

match(0,2) = true;

回溯到第2步。

match(0,0) = true;

問題解決
在這裏插入圖片描述

2.3 代碼

class Solution {
    Result[][] memo;

    public boolean isMatch(String text, String pattern) {
        memo = new Result[text.length() + 1][pattern.length() + 1];
        return dp(0, 0, text, pattern);
    }

    public boolean dp(int i, int j, String text, String pattern) {
        if (memo[i][j] != null) {
            return memo[i][j] == Result.TRUE;
        }
        boolean ans;
        if (j == pattern.length()){
            ans = i == text.length();
        } else{
            boolean first_match = (i < text.length() &&
                                   (pattern.charAt(j) == text.charAt(i) ||
                                    pattern.charAt(j) == '.'));

            if (j + 1 < pattern.length() && pattern.charAt(j+1) == '*'){
                ans = (dp(i, j+2, text, pattern) ||
                       first_match && dp(i+1, j, text, pattern));
            } else {
                ans = first_match && dp(i+1, j+1, text, pattern);
            }
        }
        memo[i][j] = ans ? Result.TRUE : Result.FALSE;
        return ans;
    }
}

3. 自底向上

3.1思路

  其實很簡單粗暴,即從最後一個字符開始反向匹配,還是以剛纔的栗子爲例,從i = 3, j = 5 開始依次往左往上循環計算,match(3,5) == true,核心的邏輯並沒有變。因爲最邊緣的值的匹配都是可以直接計算出來的

3.2 代碼

class Solution {
    public boolean isMatch(String text, String pattern) {
        boolean[][] memo = new boolean[text.length() + 1][pattern.length() + 1];
        memo[text.length()][pattern.length()] = true;

        for (int i = text.length(); i >= 0; i--){
            for (int j = pattern.length() - 1; j >= 0; j--){
                boolean curMatch = (i < text.length() &&
                                       (pattern.charAt(j) == text.charAt(i) ||
                                        pattern.charAt(j) == '.'));
                if (j + 1 < pattern.length() && pattern.charAt(j+1) == '*'){
                    memo[i][j] = memo[i][j+2] || curMatch && memo[i+1][j];
                } else {
                    memo[i][j] = curMatch && memo[i+1][j+1];
                }
            }
        }
        return memo[0][0];
    }
}

4. 動態規劃

4.1 狀態

f[i][j]表示s1的前i個字符,和s2的前j個字符,能否匹配

4.2 轉移方程

如果s1的第 i 個字符和s2的第 j 個字符相同,或者s2的第 j 個字符爲 “.”
f[i][j] = f[i - 1][j - 1]
如果s2的第 j 個字符爲 *
若s2的第 j 個字符匹配 0 次第 j - 1 個字符
f[i][j] = f[i][j - 2] 比如(ab, abc*)
若s2的第 j 個字符匹配至少 1 次第 j - 1 個字符,
f[i][j] = f[i - 1][j] and s1[i] == s2[j - 1] or s[j - 1] == '.'
這裏注意不是 f[i - 1][j - 1], 舉個例子就明白了 (abbb, ab*) f[4][3] = f[3][3]

4.3 初始化

f[0][i] = f[0][i - 2] && s2[i] == *
即s1的前0個字符和s2的前i個字符能否匹配

4.4 結果

f[m][n]

4.5 代碼

 public static boolean isMatch1(String s, String p) {
        int m = s.length(), n = p.length();
        //因爲匹配是從0開始,到最後字符長度m(n)時,所以是m+1(n+1)
        boolean[][] f = new boolean[m + 1][n + 1];

        f[0][0] = true;
        for(int i = 2; i <= n; i++){
            //s1的前0個字符和s2的前i個字符能否匹配(只有當p的前一個字符爲*時才匹配,*可以匹配前面的0個)
            f[0][i] = f[0][i - 2] && p.charAt(i-1) == '*';
        }

        for(int i = 1; i <= m; i++){
            for(int j = 1; j <= n; j++){
                if(s.charAt(i - 1) == p.charAt(j - 1) || p.charAt(j - 1) == '.'){
                    f[i][j] = f[i - 1][j - 1];
                }
                if(p.charAt(j - 1) == '*'){
                    f[i][j] = f[i][j - 2] ||
                            f[i - 1][j] && (s.charAt(i - 1) == p.charAt(j - 2) || p.charAt(j - 2) == '.');
                }
            }
        }
        return f[m][n];
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章