我试图了解一个着名的正则表达式匹配DP算法。 以防万一,人们不知道这里的描述和算法。
'.' Matches any single character.
'*' Matches zero or more of the preceding element.
The matching should cover the entire input string (not partial).
The function prototype should be:
bool isMatch(const char *s, const char *p)
Some examples:
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
static boolean isMatch(String s, String p) {
boolean[][] dp = new boolean[s.length() + 1][p.length() + 1];
dp[0][0] = true;
for (int i = 1; i < dp[0].length; i++) {
if (p.charAt(i - 1) == '*') {
dp[0][i] = dp[0][i - 2];
}
}
for (int i = 1; i < dp.length; i++) {
for (int j = 1; j < dp[0].length; j++) {
char schar = s.charAt(i - 1);
char pchar = p.charAt(j - 1);
if (schar == pchar || pchar == '.') {
dp[i][j] = dp[i - 1][j - 1];
} else if (pchar == '*') {
if (schar != p.charAt(j - 2) && p.charAt(j - 2) != '.') {
// - a b *
// - t f f f
// a f t f t // b != a and b != '.' thus treat b* as 0 match
dp[i][j] = dp[i][j - 2];
} else {
// - a b *
// - t f f f
// a f t f t
// b f f t t // dp[i][j - 2] 0 match or dp[i][j - 1] 1 math or dp[i - 1][j] 2+ match (not sure why)
dp[i][j] = dp[i][j - 2] || dp[i][j - 1] || dp[i - 1][j];
}
}
}
}
return dp[s.length()][p.length()];
}
我理解的大部分内容,但这一部分我没有得到它
dp[i][j] = dp[i][j - 2] || dp[i][j - 1] || dp[i - 1][j];
dp[i - 1][j]
人们说这将涵盖2场比赛,但不理解这一部分。有人可以解释为什么我需要检查dp[i - 1][j]
吗?
答案 0 :(得分:1)
我会使用更多的非正式表示法,所以请耐心等待。 首都将表示字符串(在模式中可能包括特殊字符)和小写字母,'。'和'*'将代表自己。
假设我们将Ax与Bx匹配,这是一个以A开头的字符串(它本身是一个字符串,如xyzz),以'x'结尾,其中一个模式以B开头(它本身就是一个模式,用于例如,x。*)以'x'结尾。结果与匹配A到B的结果相同(因为我们别无选择,只能将x与x匹配)。
我们可以写如下:
isMatch(Ax, Bx) = isMatch(A, B)
同样,将Ax与By匹配是不可能的。
isMatch(Ax, Bx) = false
够容易。所以这将对应于两个嵌套循环中的第一个if语句。
现在让我们以星号为例。 将Ax与By *匹配只能通过忽略y *(取零y)来完成,即将Ax与B匹配。
isMatch(Ax, By*) = isMatch(Ax, B)
但是如果y被点或x替换,则有选择。 我们将以Ax和Bx *为例。这两个选项匹配Ax到B(意味着采用零x)或匹配A到Bx *(意味着采用x,但我们仍然可以采取更多,因此模式不会改变):
isMatch(Ax, Bx*) = isMatch(Ax, B) || isMatch(A, Bx*)
在您的代码中,最后一个将转换为:
dp[i][j] = dp[i][j - 2] || dp[i - 1][j]
所以现在我想知道你的问题是否真的与dp[i][j - 1]
有关,因为这会让我感到困惑。
我可能错了,但似乎没必要。
它的意思是删除星号,即改变“零或更多” “正好一个”,已经被其他两个案件所涵盖,第二个是第一个。
答案 1 :(得分:0)
此处假设字符串不包含特殊字符&#39; 。 &#39; 和&#39; *&#39; ,因为否则所提供的代码无法正常工作!!
dp[i][j]
代表什么?
它表示如果只考虑字符串的第一个 i 字符和模式的 j 字符,它们是否匹配?
我们遇到&#39; *&#39;在模式中:
案例1:在&#39;之前只占用0个字符模式中的*&#39; 。
&#39; *&#39; 单独并不意味着什么。它取决于其前面的特征。
dp[i][j-2]
将完全忽略模式中的前一个字符,因为它只考虑第一个 j-2 字符,因此前面的字符与&#39;模式中的*&#39; (第j 字符)将被忽略。
现在,如果是&#39;之前的字符串和字符中的 ith 字符。 *&#39; 恰好与&#39;之前的相同或字符相同模式中的*&#39; 是&#39; 。 &#39; 然后再添加一个案例。
此处观察:。* 可以匹配任何字符串
如果满足以上条件,请考虑以下情况。
案例2:继续使用&#39;之前的字符*&#39; 一次或多次。
dp[i-1][j]
代表了这一点。请记住,模式中第j个字符是&#39; *&#39; 。
因此,如果字符串的第一个 i-1 字符与模式中的第一个 j 字符匹配,其中第j个字符为&# 39; *&#39; (这表明我们使用过&#39; *&#39; 之前的字符至少一次),然后我们可以说第一个 i < / strong>字符串中的字符与模式的第一个 j 字符匹配,因为我们已经确保 ith 字符与前面的字符匹配&#39;模式中的*&#39; 。
案例dp[i][j-1]
是多余的,并在案例2中介绍。
注意:dp[i][j-1]
dp[i][j-1]
仅匹配&#39;之前的字符一次*&#39; 。它已在dp[i-1][j]
中介绍。
原因:
我们知道 ith 字符匹配 j-1th 字符(请记住,我们在考虑此情况之前已经检查过)。
dp[i][j-1]
= dp[i-1][j-2]
已经在dp[i-1][j]
的计算中考虑过了。
dp[i-1][j]
更强大,因为dp[i-1][j] = dp[i-1][j-2] || dp[i-2][j]
第j个字符是&#39; *&#39; 。所以这就是提供内存属性的原因,它允许我们多次使用一个字符。