簡體   English   中英

使用動態編程了解正則表達式字符串匹配

[英]Understanding regex string matching using Dynamic Programming

我遇到了這個問題,要求你實現一個支持'。'的正則表達式匹配器。 和'*',在哪里

'' 匹配任何單個字符。

'*'匹配前面元素的零個或多個。

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

雖然我能夠以線性方式解決這個問題,但我遇到了許多使用DP的解決方案,如下所示,

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

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

我很難理解這一點。 我已經解決了一些涉及網格的DP問題(2d網格中的最短路徑,二進制2d網格中的最大正方形),使用DP表對我來說非常有意義。 但是在這里我完全迷失了,我無法理解遍歷2d表如何幫助解決這個問題。 更進一步看來我們知道字符在循環中不匹配,所以我不明白為什么我們不在那里終止搜索(這也可能是由於我對表遍歷導致如何導致一個解法)。 對於像這樣的問題有明確的直觀解釋嗎?

使用DP來解決這樣的問題的直覺是找出以下問題的答案

  1. 可以使用遞歸來解決問題嗎? 這意味着它可以用相同類型的較小子問題來表示嗎?
  2. 在遞歸樹中重復出現較小的子問題嗎? 如果是,則可以以這樣的方式存儲較小問題的結果:每當遇到類似的子問題時,可以在O(1)中訪問結果。 這通常稱為memoization。

讓我們首先了解問題的解決方案,你可以在線性方式解決時找到解決方案。

  1. 在將文本與模式匹配時,第一個字符將匹配或不匹配。

    案例1:第一個字符匹配或模式的第一個字符是'。'

    案例1.1下一個字符是'*'

    案例1.2下一個字符不是'*'

    案例2:第一個字符不匹配

    案例2.1下一個字符是'*'

    案例2.2下一個字符不是'*'

現在讓我們弄清楚前面針對上述問題討論的兩個步驟。

對於案例1.1的例子是

isMatch("a", "a*a")isMatch("aab", "a*b")相當於求解

isMatch("a", "a") || isMatch("", "a*a") isMatch("a", "a") || isMatch("", "a*a")

isMatch("aab", "b") || isMatch("ab", "a*b") isMatch("aab", "b") || isMatch("ab", "a*b")分別。 ||第一部分 condition包含模式中可選字符的場景,后跟'*',第二部分包含重復匹配字符的場景。

類似地,可以為所有情況形成子問題。 案例2.2應該直接返回虛假。

下面是帶有遞歸方法的java代碼

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

private boolean isMatch(String text, String pattern, int ti, int pi) {

    if (pi == pattern.length() && ti < text.length()) return false;

    if (ti == text.length() && pi == pattern.length()) return true;

    if (ti == text.length()) {
        return isNextCharStar(pattern, pi) && isMatch(text, pattern, ti, pi + 2);
    }


    boolean result;
    final boolean hasFirstMatched = text.charAt(ti) == pattern.charAt(pi) || pattern.charAt(pi) == '.';

    if (isNextCharStar(pattern, pi)) {
        result = isMatch(text, pattern, ti, pi + 2);
        if (hasFirstMatched) {
            result = result || isMatch(text, pattern, ti + 1, pi);
        }
        return result;
    }

    return hasFirstMatched && isMatch(text, pattern, ti + 1, pi + 1);

}

private boolean isNextCharStar(String pattern, int pi) {
    return pi < pattern.length() - 1 && pattern.charAt(pi + 1) == '*';
}

現在讓我們應用memoization步驟。 如果我們在返回之前開始保存結果,我們可以在下次重用它。 我們應該如何以及在哪里保存它? 對於tipi所有可能組合,我們必須存儲結果。 其中ti是文本索引, pi是模式索引。 所以一個大小為text.length * pattern.length的二維數組就足夠了。 以下是上述更改后的代碼

Boolean [][] dp;

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

private boolean isMatch(String text, String pattern, int ti, int pi) {

    if (pi == pattern.length() ) return ti == text.length();

    if (ti == text.length()) {
        return isNextCharStar(pattern, pi) && isMatch(text, pattern, ti, pi + 2);
    }

    if(dp[ti][pi] != null) return dp[ti][pi];

    boolean result;
    final boolean hasFirstMatched = text.charAt(ti) == pattern.charAt(pi) || pattern.charAt(pi) == '.';

    if (isNextCharStar(pattern, pi)) {
        result = isMatch(text, pattern, ti, pi + 2);
        if (hasFirstMatched) {
            result = result || isMatch(text, pattern, ti + 1, pi);
        }
        dp[ti][pi] = result;
        return result;
    }

    dp[ti][pi] = hasFirstMatched && isMatch(text, pattern, ti + 1, pi + 1);
    return dp[ti][pi];

}

private boolean isNextCharStar(String pattern, int pi) {
    return pi < pattern.length() - 1 && pattern.charAt(pi + 1) == '*';
}

如果您仔細觀察,只需更改3行,使其成為遞歸解決方案的DP解決方案。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM