我遇到了这个问题,要求你实现一个支持'。'的正则表达式匹配器。和'*',其中
''匹配任何单个字符。
'*'匹配前面元素中的零个或多个。
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表如何帮助解决这个问题。更进一步看来我们知道字符在循环中不匹配,所以我不明白为什么我们不在那里终止搜索(这也可能是由于我对表遍历导致如何导致一个办法)。对于像这样的问题有明确的直观解释吗?
答案 0 :(得分:2)
使用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("aab", "b") || isMatch("ab", "a*b")
。 ||
条件的第一部分涵盖了模式中可选字符的场景,因为它后跟'*',第二部分涵盖了重复匹配字符的场景。
同样可以为所有案例形成子问题。案例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解决方案。