动态规划之正则表达式匹配
动态规划之正则表达式匹配
leetcode-10 - 困难
给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。
'.' 匹配任意单个字符
'*' 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。
动态规划
该题属于 leetcode 中的困难难度的题目,使用动态规划解决的关键在于找到合适的动态转移方程。
步骤一 定义子问题
将原问题的规模缩小为一个子问题。原问题是判断字符串s和字符规律p是否匹配,则子问题可以描述为判断字符串s(m)和字符规律p(n)是否匹配,其中 0 <= m < s.length(),0 <= n < p.length()。
与原问题相比,子问题是参数化的。动态规划是通过解决所有的子问题,进而解决原问题。因此,子问题需要具备两个特点:
- 原问题可以由子问题表示。当
m == s.length()-1,n == p.length()-1时,子问题也就变成了原问题。 - 一个子问题的解要能通过其他子问题的解得到。通过对 p[n] 分情况讨论可以由前面已解决的子问题的解推导出 s[m] 和 p[n] 是否匹配。
步骤二 确定子问题的递推关系(动态转移方程)
通过观察,确定子问题”s(m)与p(n)是否匹配”是否可以由前面已经解决的子问题来解决。
由于字符''的存在,字符规律p(n)可以与字符串s(m)不等长,此时会比较难以判断,因此我们可以根据p(n)的最后一个字符p[n]是否为''来对字符规律p(n)进行分类讨论:
-
p[n]!='*'
这里根据p[n]是否为'.'又可以分为两种情况:-
p[n]=='.'
由于'.'可以匹配任何字符,因此,"s(m)与p(n)是否匹配"取决于"s(m-1)和p(n-1)是否匹配":

-
p[n]!='.'
此时,"s(m)与p(n)是否匹配"不仅取决于"s(m-1)和p(n-1)是否匹配",还要看"s[m]与p[n]是否相等"。

-
-
p[n]=='*''*'可以匹配零个或多个前面的字符,匹配零个和匹配多个有所区别,因此可以将其分开讨论:
-
'*'匹配零个前面的字符,也即可以将前面的字符"消除"掉,这样只需判断s(m)与p(n-2)是否匹配即可:

-
'*'匹配至少一个前面的字符,这样,匹配字串一定是以与p[n-1]相同的字符结尾(但不一定就在p[n-1]处结尾):

上图紫色框中至少有一个p[n-1]的字符,则灰色框中有零个或多个p[n-1],正好符合'*'的匹配规则,因此上图可以转换为下图:

我们只需判断s[m]与p[n-1]是否相等以及s(m-1)与p(n)是否匹配即可。
-
我们使用f[i][j]表示s(i)和p(j)是否匹配(i 即上图 m,j 即上图 n),综合上述讨论,我们可以得到如下的递推关系:

步骤三 寻找边界条件
-
p[j]!='*'
我们需要对f[i-1][j-1]分情况讨论:
有可能为0,因此我们需要事先知道f[-1][j-1](j>0)、f[i-1][-1](i>0)以及f[-1][-1]的所有值。- 如果
i=j=0,则f[-1][-1]表示 s 和 p 均为空时的情形,此时两者是匹配的,f[-1][-1]=true。 - 如果
i>0,j=0,f[i-1][-1]表示 s 不为空,p 为空的情形,此时两者是绝对不匹配的,f[i][-1]=false。 - 如果
i=0,j>0,f[-1][j-1]表示 s 为空,p 不为空的情形,此时两者是有可能匹配的,因为 p 中可能存在 '*' 将前面的字符消除掉的的情况,如"a*"或"a*b*"等。但是,这种情况符合状态转移方程(2),完全可以根据f[i][j-2]进行判断,而f[i][j-2]同样符合状态转移方程(2)(j-2>0的情况下),直到j-2=-1,而f[-1][-1]=true。因此,此种情况是可以由转移方程得到正确的结果,不需要事先进行判断。 - 如果
i>0,j>0,f[i-1][j-1]的值在之前的子问题中已然解决,可直接使用。
- 如果
-
p[j]='*'
根据题目条件,'*'是不可能出现在 p 的第一个字符位置的,可推出j>0,即f[i][j-2]中j-2的最小值为-1,match(s[i],p[j-1])中j-1的最小值为0,而f[i-1][j]中的j-1的最小值为-1,这些值在前面已然讨论。
为了方便对 s 和 p 为空时进行判断,我们将上面讨论的 f 数组的索引全部加1,使其从0开始,即使用f[0][0]表示 s 和 p 均为空时两者是否匹配,使用f[1][1]表示 s 的子串 s(1) 和 p 的子串 p(1) 是否匹配,......。如下图所示,灰色区域是必然为false,红圈处除了f[0][0]必然为true外,要看其字符是否为'*'以及其前方第二个位置的值是否为true,而黄色区域则可以完全使用转移方程来判断。事实上,第一行除了f[0][0]需要特别指定值外,是可以与转移方程的代码合并的。

代码
这里的代码直接使用leetcode题解中的官方代码,自己的代码在判断边界条件方面不够简洁:
class Solution {
public boolean isMatch(String s, String p) {
int m = s.length();
int n = p.length();
boolean[][] f = new boolean[m + 1][n + 1];
f[0][0] = true;
for (int i = 0; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
if (p.charAt(j - 1) == '*') {
f[i][j] = f[i][j - 2];
if (matches(s, p, i, j - 1)) {
f[i][j] = f[i][j] || f[i - 1][j];
}
} else {
if (matches(s, p, i, j)) {
f[i][j] = f[i - 1][j - 1];
}
}
}
}
return f[m][n];
}
public boolean matches(String s, String p, int i, int j) {
if (i == 0) {
return false;
}
if (p.charAt(j - 1) == '.') {
return true;
}
return s.charAt(i - 1) == p.charAt(j - 1);
}
}
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/regular-expression-matching/solution/zheng-ze-biao-da-shi-pi-pei-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
本文来自博客园,作者:champock,转载请注明原文链接:https://www.cnblogs.com/champock/articles/15473681.html
浙公网安备 33010602011771号