本人小扒菜,写的不好大家多多指导,话不多说上题目。
请实现一个函数用来匹配包含'. '和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(含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
的小写字母以及字符.
和*
,无连续的'*'
。
解题思路
1、两种方法求解:
(一)、递归,根据p[pi]的下一位p[pi+1]是否为''来进行可能性划分,确保下一次函数调用时p[pi]不会为'',这样递归不会产生后效性的问题;
(二)、将前面递归的方法改成dp填表,注意填表时不需要考虑p[pi]为'*’的情况;
代码:
class Solution {
public boolean isMatch(String s, String p) {
// base case
if (s == null || p == null) {
return false;
}
char[] sArr = s.toCharArray();
char[] pArr = p.toCharArray();
int kuan = sArr.length;
int chang = pArr.length;
boolean[][] dp = new boolean[kuan+1][chang+1];
return dpMatch(sArr, pArr, dp);
// return recurMatch(sArr, pArr, 0, 0);
}
// 递归解
public boolean recurMatch(char[] s, char[] p, int si, int pi) {
// base case
if (pi == p.length) {
return si == s.length;
}
// pi的后一位不是'*'
// 这里就考虑来pi处于字符p最后一位的情况,那么整个递归pi始终不可能为*,后面递归改写为填表的时候,就可以利用这个性质
if (pi+1 == p.length || p[pi+1] != '*') {
return si != s.length
&& (s[si] == p[pi] || p[pi] == '.')
&& recurMatch(s, p, si+1, pi+1);
}
// pi的后一位是'*'
// 当p[pi]和s[si]相等的时候
// 循环遍历pi和pi+1('*')合起来对应s不同数量字符时的情况,直到无法满足不了循环条件退出
while (si != s.length && (s[si] == p[pi] || p[pi] =='.')) {
if (recurMatch(s, p, si, pi+2)) {
return true;
}
si++;
}
// pi的后一位是'*'
// 当p[pi]和s[si]不相等的时候
return recurMatch(s, p, si, pi+2);
}
public boolean dpMatch(char[] s, char[] p, boolean[][] dp) {
// init dp[][]
int sLen = s.length;
int pLen = p.length;
// p[pi]和s[si]都越界为空的时候
dp[sLen][pLen] = true;
// 最后一列,p[pi]越界为空了
for (int i = 0; i < s.length - 1; i++) {
dp[i][pLen] = false;
}
// 最后一行,s[si]越界为空了
for (int i = p.length-2; i >=0 ; i=i-2) {
if (p[i] != '*' && p[i+1] == '*') {
dp[sLen][i] = true;
} else {
break;
}
}
// 倒数第二列,p[pi]是最后一位
if (sLen >0 && pLen >0) {
if (p[pLen-1] == '.' || p[pLen-1] == s[sLen-1]) {
dp[sLen-1][pLen-1] = true;
}
}
// --------*初始化结束,开始递归过程转dp填表*----------
// 从下到上,从右到左的顺序
for (int curR = sLen-1; curR >=0 ; curR--) {
for (int curC = pLen-2; curC>=0; curC--) {
// p[pi+1]的值不等于'*'
if (p[curC+1] != '*') {
dp[curR][curC] = (s[curR] == p[curC] || p[curC] == '.')
&& dp[curR+1][curC+1];
} else { // p[pi+1]的值等于'*'
// 当p[pi]和s[si]相等的时候
int si = curR;
while (si != sLen && (s[si] == p[curC] || p[curC] == '.')) {
if (dp[si][curC+2]) {
dp[curR][curC] = true;
break;
}
si++;
}
// 当p[pi]和s[si]不相等的时候
if (dp[curR][curC] != true) {
dp[curR][curC] = dp[si][curC+2];
}
}
}
}
return dp[0][0];
}
}
复杂度分析
时间复杂度:O(mn)O(mn),其中 mm 和 nn 分别是字符串 ss 和 pp 的长度。我们需要计算出所有的状态,并且每个状态在进行转移时的时间复杂度为 O(1)O(1)。
空间复杂度:O(mn)O(mn),即为存储所有状态使用的空间。