项目地址:https://github.com/SpecialYy/Sword-Means-Offer
题目
请实现一个函数用来匹配包括’.’和’*’的正则表达式。模式中的字符’.’表示任意一个字符,而’*’表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串”aaa”与模式”a.a”和”ab*ac*a”匹配,但是与”aa.a”和”ab*a”均不匹配
解析
预备知识
这道题考察的是正则表达式匹配引擎的原理,只不过这里为了简单,仅仅涉及到”. or *”。
该题目如果没有理清思路,就会陷入各种复杂的情况。
思路一
首先不着急想到全部情况,而是由易到难。
情况一:假设模式串没有特殊符号
这种就是很简单的情况,直接将字符串中每一位去匹配模式串每一位即可。若某一位不相等,则说明不匹配,直接结束;否则字符串和模式串同时后移一位继续匹配下一位。
情况二:假设模式串含有’.’
由于’.’可以表示任意字符,所以在第一种情况中,若某一位不相等,则判断当前模式串是否是’.’,若是则认为可以匹配,字符串和模式串同时后移一位继续匹配下一位即可。
假设模式串含有’.’和’*’
因为’*’可以表示0或者多个它前面出现的字符,所以情况有些许复杂。但只要分类合理,也能迎刃而解。由于’*’的的特殊性,所以我们对于每一个待匹配的字符,都要判断模式串的下一位是否为’*’。
- 若模式串中待匹配字符j的下一个不是’*’,则直接按情况二进行匹配。
- 若模式串中待匹配字符j的下一个是’*’:
- 若字符串i与模式串j相等或者模式串j为’.’,可以有2种操作:
- 字符串i后移一位,模式串j位置不变,相当于模式不变,意味着此处*匹配多个字符
- 字符串i不变,模式串j后移2位,相当于跳过*匹配,意味着此处匹配0个字符
- 你可能会问如果*只匹配一个字符呢,为什么没有字符串i后移一位,模式串j后移2位情况呢,因为该情况可以看作先执行一次情况1,再执行一次情况2得到。
- 若字符串i与模式串j不相等,则相当于*匹配0个字符,字符串i不变,模式串j后移2位即可。
- 若字符串i与模式串j相等或者模式串j为’.’,可以有2种操作:
递归处理结束条件:
- 当字符串和模式串同时走到了末尾,说明匹配成功
- 当字符串没有走到末尾,而模式串已经走到了末尾,则说明匹配不成功
- 这种就是模式串待匹配j的下一位不是’*’, 且j与字符串i的不想等且j不为’.’,说明匹配不成功。
/**
* 有限状态自动机做法
* @param str
* @param pattern
* @return
*/
public static boolean match2(char[] str, char[] pattern) {
if(str == null || pattern == null) {
return false;
}
return matchCore(str, 0, pattern, 0);
}
public static boolean matchCore(char[] str, int index1, char[] pattern, int index2) {
if(index1 == str.length && index2 == pattern.length) {
return true;
}
if(index1 != str.length && index2 == pattern.length) {
return false;
}
//若待匹配的下一位为'*'
if((index2 + 1) < pattern.length && pattern[index2 + 1] == '*') {
if(index1 < str.length && (str[index1] == pattern[index2] || pattern[index2] == '.')) {
return matchCore(str, index1 + 1, pattern, index2)
|| matchCore(str, index1, pattern, index2 + 2);
}else {
return matchCore(str, index1, pattern, index2 + 2);
}
}
//若待匹配的下一位不为'*'
else {
if(index1 < str.length && (str[index1] == pattern[index2] || pattern[index2] == '.')) {
return matchCore(str, index1 + 1, pattern, index2 + 1);
} else {
return false;
}
}
}
思路二
思路二仅仅是换种角度来理解上述代码,比如ab*ac*a模式的有限状态自动机为:
1,2,3…表示中间状态,S表示开始状态,E表示终止状态。通过这个有限状态自动机可以清晰的理解带有*的情况。
- 匹配a之后即可进入1状态,
- 之后碰到自旋转状态(因为b的下一个字符为*),所以这里可以有3种操作。第一种就是:匹配一个b,且状态不变,相当于匹配多个。第二种就是:匹配一个b,且状态变为2,相当于匹配一个。第三种:不匹配字符,直接进入状态2。
- 状态2匹配一个a进入状态3
- 状态3又是一个自旋转状态,跟状态2类似,这里不再累述。
- 状态4匹配一个a进入终止状态,即匹配完毕
以上就是以有限状态自动机的角度来进行匹配,可以说是非常好理解的机制了。
总结
只要能够合理对各种情况进行分类,就能写出清晰的代码。