题目描述
- 请实现一个函数用来匹配包括’.’和’‘的正则表达式。模式中的字符’.’表示任意一个字符,而’‘表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串”aaa”与模式”a.a”和”ab*ac*a”匹配,但是与”aa.a”和”ab*a”均不匹配
- 地址: 牛客链接
问题分析
- 这题的难点在于如何处理 * 的情况
- 先从递归尝试,递归函数 match(str, pattern, sIndex, pIndex) 表示 str[pIndex ~ str.length - 1] 能否匹配 pattern[pIndex ~ pattern.length - 1]。这样依靠递归便完成了问题规模的缩小
- 那么如何寻求递归关系呢?
- 根据pIndex +1 位置上是否为 ‘*’ 来分类
- 如果 pIndex 为pattern最后位置,或者 pIndex + 1 位置上不是 ‘*’,那么当前只需比较 s[sIndex] 是否能匹配 p[pIndex] (当然,比较前先对sIndex判断是否越界)。只有满足当前位置字符能匹配,并且后序字符也能匹配时(递归),从当前位置开始的后序字符才算匹配。例如 abc 与 abc 的匹配,abc 与 *bc 的匹配
sIndex != s.length && (s[sIndex] == p[pIndex] || p[pIndex] == '.') && match(s, p, sIndex + 1, pIndex + 1)
- 如果pIndex + 1 位置上是 ‘’,那么需要考虑str中从sIndex开始后连续的多个字符与 pattern[pIndex] 相匹配的情况,例如 a*bb 与 aaaab 的匹配。所以需要用循环来枚举 带来的所有可能性,例如 a* 可以与 a,aa,aaa,aaaa匹配,对上述枚举的结果都要进行递归,一旦发现一个匹配的上,便停止枚举。否则一直到指针指向str末尾或者指向不匹配的字符b为止。 但有要注意的一点是,例如对于 a*aaa 与 aaa,可能pIndex位置匹配成功,但是我们有可能不要,*之前的字符 ‘a’ 出现0次才能匹配的上。
int i = sIndex;
while (i != s.length && (s[i] == p[pIndex] || p[pIndex] == '.')) {
if (match(s, p, i + 1, pIndex + 2)) {
return true;
}
i++;
}
return match(s, p, sIndex, pIndex + 2);
}
if (pIndex == p.length) {
return sIndex == s.length;
}
- 上述是递归行为,那么如何用动态规划 dp 来优化递归?
- dp[i][j] 表示 str[i ~ str.length - 1] 能否与 pattern[j ~ pattern.length - 1] 相匹配。经过递归关系可知,dp[i][j] 主要依赖于 dp[i+1][j+1] (当j+1 为 pattern.length 或者 j+1处不为 * 时) 或者 依赖 dp[i][j+2], dp[i+1][j+2],…所以从二维表中可以看出,只依赖于它左边与下边的位置。所以更新dp二维数组的顺序是从右到左,从下到上。
- 那么如何确定初始条件呢?
根据递归的base case 可知,初始条件便知道一列即可,除了右下角位置为 true,最后一列都为 false
- 关于初始条件的填写,有个选择性问题要记住:
- 初始条件可以根据题意写和递归 base case写
- 如果你初始条件填的很少,那么以后填整张表时,有可能会越界,所以,必须填表时判断越界问题。
- 如果你初始条件填的很多,那么便不会出现越界问题
- 例如该题,如果你初始(根据题意)填上最后一行,与最后两列,那么不会出现越界问题。如果只填最后一列,在填最后一行或者倒数第二列的时候,肯定会越界,必须防止越界。
经验教训
- 如何缩小问题规模(确定递归函数的形式),如何找到递归关系(根据题意),重中之重!!!
- 改写dp时,初始条件填写的选择性问题
代码实现:
public boolean match1(char[] str, char[] pattern) {
if (str == null || pattern == null) {
return false;
}
return match(str, pattern, 0, 0);
}
public boolean match(char[] s, char[] p, int sIndex, int pIndex) {
if (pIndex == p.length) {
return sIndex == s.length;
}
if (pIndex + 1 == p.length || p[pIndex + 1] != '*') {
return sIndex != s.length && (s[sIndex] == p[pIndex] || p[pIndex] == '.') &&
match(s, p, sIndex + 1, pIndex + 1);
}
int i = sIndex;
while (i != s.length && (s[i] == p[pIndex] || p[pIndex] == '.')) {
if (match(s, p, i + 1, pIndex + 2)) {
return true;
}
i++;
}
return match(s, p, sIndex, pIndex + 2);
}
public boolean match(char[] str, char[] pattern) {
if (str == null || pattern == null) {
return false;
}
boolean[][] dp = new boolean[str.length + 1][pattern.length + 1];
dp[str.length][pattern.length] = true;
for (int i = str.length; i >= 0; i--) {
for (int j = pattern.length - 1; j >= 0; j--) {
if (j + 1 == pattern.length || pattern[j + 1] != '*') {
dp[i][j] = i != str.length && (str[i] == pattern[j] || pattern[j] == '.') && dp[i + 1][j + 1];
continue;
}
int index = i;
while(index < str.length && (str[index] == pattern[j] || pattern[j] == '.')) {
if (dp[index + 1][j + 2]) {
dp[i][j] = true;
break;
}
index++;
}
dp[i][j] = dp[i][j] == true ? true : dp[i][j+2];
}
}
return dp[0][0];
}