[英]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:第一個字符匹配或模式的第一個字符是'。'
案例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步驟。 如果我們在返回之前開始保存結果,我們可以在下次重用它。 我們應該如何以及在哪里保存它? 對於ti
和pi
所有可能組合,我們必須存儲結果。 其中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.