@date: 8/18/2020 10:26:00 AM
@难度: 困难
考察内容: 字符串 动态规划
@e-mail: lwyz521604#163.com
题目来自《剑指offer》 电子工业出版社
请实现一个函数用来匹配包含'. '
和'*'
的正则表达式。模式中的字符'.'
表示任意一个字符,而'*'
表示它前面的字符可以出现任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"
与模式"a.a"
和"ab*ac*a"
匹配,但与"aa.a"
和"ab*a"
均不匹配。
示例 1:
输入:
s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。
示例 2:
输入:
s = "aa"
p = "a*"
输出: true
解释: 因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。
示例 3:
输入:
s = "ab"
p = ".*"
输出: true
解释: ".*" 表示可匹配零个或多个('*')任意字符('.')。
示例 4:
输入:
s = "aab"
p = "c*a*b"
输出: true
解释: 因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。因此可以匹配字符串 "aab"。
示例 5:
输入:
s = "mississippi"
p = "mis*is*p*."
输出: false
- s 可能为空,且只包含从 a-z 的小写字母。
- p 可能为空,且只包含从 a-z 的小写字母以及字符 . 和 ,无连续的 '’。
递归解法
从示例来看,题目中的.*
指的应该是任意的字符串,而不是只含有相同字符的字符串。这道题可以通过逐个判断模式字符串中前两个字符的情况,然后递归地来解:
class Solution {
public boolean isMatch(String s, String p) {
return this.arrayMatch(s.toCharArray(), p.toCharArray(), 0, 0);
}
private boolean arrayMatch(char[] arrS, char[] arrP, int ptrS, int ptrP){
int n1 = arrS.length;
int n2 = arrP.length;
if(ptrP==n2){ // 模式为空时,字符串必须为空
return n1 == ptrS;
}
if(n1-ptrS==0){ // 字符串为空,模式非空时,模式必须是 某* 的形式
if(n2-ptrP>=2 && arrP[ptrP+1]=='*'){
return arrayMatch(arrS, arrP, ptrS, ptrP+2);
}
return false;
}
// 如果模式字符串只剩最后一个位置
if(n2-ptrP==1){
if(n1-ptrS==1){
if(arrP[ptrP]=='.')
return true;
else
return arrP[ptrP]==arrS[ptrS];
}
return false;
}
// 模式字符串还剩下两位或超过两位的情况
if(arrP[ptrP]=='.'){
if(arrP[ptrP+1]=='*'){
// 题目对这种情况的意思似乎是任意的字符出现任意次
return arrayMatch(arrS, arrP, ptrS+1, ptrP+2)
|| arrayMatch(arrS, arrP, ptrS, ptrP+2)
|| arrayMatch(arrS, arrP, ptrS+1, ptrP);
}else{
return arrayMatch(arrS, arrP, ptrS+1, ptrP+1);
}
}else{
if(arrP[ptrP+1]=='*'){
if(arrS[ptrS]==arrP[ptrP]){
return arrayMatch(arrS, arrP, ptrS+1, ptrP+2)
|| arrayMatch(arrS, arrP, ptrS, ptrP+2)
|| arrayMatch(arrS, arrP, ptrS+1, ptrP);
}else{
return arrayMatch(arrS, arrP, ptrS, ptrP+2);
}
}else{
if(arrS[ptrS]==arrP[ptrP]){
return arrayMatch(arrS, arrP, ptrS+1, ptrP+1);
}else{
return false;
}
}
}
}
}
提交结果如下所示,时间效率并不高,原因应该是在递归调用中的几个“或”语句下面存在重复求解子问题的情况,因此可以用一个矩阵将这些子问题的求解结果存储下来,这样也就是动态规划方法了。
执行用时:846 ms, 在所有 Java 提交中击败了5.03%的用户
内存消耗:38.3 MB, 在所有 Java 提交中击败了59.69%的用户
自上而下的动态规划解法
前面已经提到,递归解法存在重复求解子问题的情况,因而可以在递归过程中增加备忘机制,使得时间效率大幅提高。《算法导论》中称这种思路为自上而下带备忘的动态规划方法。其具体实现如下,由于直接在上一个版本的基础上略加修改而成的,代码的书写不太优美,其实可以让arrayMatch
函数变成void
的。
class Solution {
public boolean isMatch(String s, String p) {
int[][] count = new int[s.length()+1][p.length()+1];
return this.arrayMatch(s.toCharArray(), p.toCharArray(), 0, 0, count);
}
private boolean arrayMatch(char[] arrS, char[] arrP, int ptrS, int ptrP, int[][] count){
int n1 = arrS.length;
int n2 = arrP.length;
if(count[ptrS][ptrP]==1)
return true;
if(count[ptrS][ptrP]==-1)
return false;
if(ptrP==n2){ // 模式为空时,字符串必须为空
if(n1 == ptrS){
count[ptrS][ptrP] = 1;
return true;
}
count[ptrS][ptrP] = -1;
return false;
}
if(n1-ptrS==0){ // 字符串为空,模式非空时,模式必须是 某* 的形式
if(n2-ptrP>=2 && arrP[ptrP+1]=='*'){
boolean temp = arrayMatch(arrS, arrP, ptrS, ptrP+2, count);
if(temp)
count[ptrS][ptrP] = 1;
else
count[ptrS][ptrP] = -1;
return temp;
}
count[ptrS][ptrP] = -1;
return false;
}
// 如果模式字符串只剩最后一个位置
if(n2-ptrP==1){
if(n1-ptrS==1){
if(arrP[ptrP]=='.') {
count[ptrS][ptrP] = 1;
return true;
}else{
if(arrP[ptrP]==arrS[ptrS]){
count[ptrS][ptrP] = 1;
return true;
}
count[ptrS][ptrP] = -1;
return false;
}
}
count[ptrS][ptrP] = -1;
return false;
}
// 模式字符串还剩下两位或超过两位的情况
if(arrP[ptrP]=='.'){
if(arrP[ptrP+1]=='*'){
// 题目对这种情况的意思似乎是任意的字符出现任意次
boolean temp = arrayMatch(arrS, arrP, ptrS+1, ptrP+2, count)
|| arrayMatch(arrS, arrP, ptrS, ptrP+2, count)
|| arrayMatch(arrS, arrP, ptrS+1, ptrP, count);
if(temp)
count[ptrS][ptrP] = 1;
else
count[ptrS][ptrP] = -1;
return temp;
}else{
boolean temp = arrayMatch(arrS, arrP, ptrS+1, ptrP+1, count);
if(temp)
count[ptrS][ptrP] = 1;
else
count[ptrS][ptrP] = -1;
return temp;
}
}else{
if(arrP[ptrP+1]=='*'){
if(arrS[ptrS]==arrP[ptrP]){
boolean temp = this.arrayMatch(arrS, arrP, ptrS+1, ptrP+2, count)
|| this.arrayMatch(arrS, arrP, ptrS, ptrP+2, count)
|| this.arrayMatch(arrS, arrP, ptrS+1, ptrP, count);
if(temp)
count[ptrS][ptrP] = 1;
else
count[ptrS][ptrP] = -1;
return temp;
}else{
boolean temp = arrayMatch(arrS, arrP, ptrS, ptrP+2, count);
if(temp)
count[ptrS][ptrP] = 1;
else
count[ptrS][ptrP] = -1;
return temp;
}
}else{
if(arrS[ptrS]==arrP[ptrP]){
boolean temp = arrayMatch(arrS, arrP, ptrS+1, ptrP+1, count);
if(temp)
count[ptrS][ptrP] = 1;
else
count[ptrS][ptrP] = -1;
return temp;
}else{
count[ptrS][ptrP] = -1;
return false;
}
}
}
}
}
在系统中提交的结果为
执行用时:2 ms, 在所有 Java 提交中击败了99.91%的用户
内存消耗:39.7 MB, 在所有 Java 提交中击败了20.39%的用户