题目:请实现一个函数用来匹配包含 ‘.’ 和 ‘*’ 的正则表达式。模式中的字符 ‘.’ 表示任意一个字符,而 ‘*’ 表示它前面的字符可以出现任意次(含0次)。在本题中。匹配是指字符串的所有字符匹配整个模式。例如,字符串 "aaa"与模式 "a.a"和 "ab*ac*a"匹配,但与 "aa.a"和 "ab*a"均不匹配。
这道题的难点非常明显,那就是要处理 '*'
这个字符的情况,当我们遇到 '*'
时,可能会碰到以下三种情况:
- 忽略
'*'
和它前面的字符,也就是题目中说的出现 0 次的情况,直接从下一个字符开始匹配。 - 如果
'*'
前面的字符和字符串当前的字符相匹配,那么我们选择将字符串后面的字符和'*'
继续进行匹配。 - 如果
'*'
前面的字符和字符串当前的字符相匹配,我们还可以选择只匹配这一次的'*'
,后面不继续进行匹配。
我们假设匹配字符串为 "ab*d"
,上面 3 种情况的有限状态机示意图如下所示:
对于字符 b,我们可以选择直接跳过去匹配下一个字符 d(对应第 1 点),也可以选择匹配一次之后接着匹配字符 d(对应第 3 点),或者在匹配之后继续匹配字符 b(对应第 2 点)。
而对于上面有多种处理 '*'
字符的情况,最简便的方法明显是使用递归的方式进行处理,就像走迷宫问题一样,把所有的情况都试过一遍,我们就知道字符串到底能不能匹配正则表达式了,实现代码如下:
public class Test {
public static void main(String[] args) {
System.out.println(match("aaaab", "a*.b"));
}
public static boolean match(String str, String pattern){
if (str.length() == 0 && pattern.length() == 0){
return true;
}
if (str.length() == 0 || pattern.length() == 0){
return false;
}
return matchCore(str, pattern, 0, 0);
}
private static boolean matchCore(String str, String pattern, int i, int j) {
// 字符串和正则表达式均已匹配到最后一个字符,说明匹配成功
if (i == str.length() && j == pattern.length()){
return true;
}
// 当正则表达式无字符可继续匹配时而字符串还未匹配完,匹配失败
if (i != str.length() && j == pattern.length()){
return false;
}
// 处理 '*' 的情况
if (j < pattern.length()-1 && pattern.charAt(j+1) == '*'){
// '*' 前的字符与字符串当前字符相等
if (pattern.charAt(j) == str.charAt(i)){
// 直接跳过,不进行匹配
return matchCore(str, pattern, i, j+2) ||
// 匹配一次后跳过
matchCore(str, pattern, i+1, j+2) ||
// 匹配一次后继续进行匹配
matchCore(str, pattern, i+1, j);
} else {
// 直接跳过,不进行匹配
return matchCore(str, pattern, i, j+2);
}
}
// 处理普通字符以及 '.' 的情况
if (pattern.charAt(j) == str.charAt(i) || pattern.charAt(j) == '.'){
return matchCore(str, pattern, i+1, j+1);
}
return false;
}
}
这段代码结合前面的分析看并不复杂,其中的 i、j 表示分布指向 str(字符串)和 pattern(正则表达式)所在位置的指针。
参考
该题目以及解题思路源于:
- 《剑指Offer》第二版
- 第 3 章 高质量的代码
- 3.3 代码的完整性
- 面试题19:正则表达式匹配
- 3.3 代码的完整性
- 第 3 章 高质量的代码