正则表达式匹配–LeetCode10
题目
给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。
'.' 匹配任意单个字符
'*' 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。
说明:
- s 可能为空,且只包含从 a-z 的小写字母。
- p 可能为空,且只包含从 a-z 的小写字母,以及字符 . 和 *。
示例:
输入:
s = "aab"
p = "c*a*b"
输出: true
解释: 因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。
因此可以匹配字符串 "aab"。
思路
方法一:递归实现
- 模式中当前位置下一个字符是“*”时:若当前字符不相等,则模式后移两个字符,继续比较;若当前字符相等,则有三种情况:(比如a 和 a*进行匹配,三种情况使用“||”进行并列比较)
- 字符串字符位置不变,模式后移两个字符,继续比较;//表示a*被忽略
- 字符串后移一个字符,模式后移两个字符,继续比较;//表示a 和 a*匹配
- 字符串后移一个字符,模式字符位置不变,继续比较。//表示a 和 a*匹配,但是星号表示不止一个a。
- 当模式中当前位置下一个字符不为“*”时:若当前字符相等,则字符串和模式都后移一个字符,继续调用函数进行比较;若不相等,则返回false。
class Solution {
public boolean isMatch(String s, String p) {
// 边界条件
if (s == null || p == null) {
return false;
}
return isMatchCore(s, 0, p, 0);
}
private boolean isMatchCore(String s, int sIndex, String p, int pIndex) {
// 递归终止条件,表示字符串和模式都递归到最后一个字符的右边了,完全匹配
if (sIndex == s.length() && pIndex == p.length()) {
return true;
}
// 模式不可以先结束,模式先结束肯定是不匹配的,但是字符串如果先结束则不一定,
// 比如:a和aa*,字符串中的指针先走到length的位置,但此时并没有结束
if (sIndex < s.length() && pIndex == p.length()) {
return false;
}
// 程序的主体是对模式判断下一个字符是否为“*”,模式中的指针最多到倒数第二个。
if (pIndex+1 < p.length() && p.charAt(pIndex+1)=='*') {
if (sIndex<s.length() && (s.charAt(sIndex)==p.charAt(pIndex)
|| p.charAt(pIndex)=='.')) {
// 下面三种情况的含义一定要明确。
return isMatchCore(s,sIndex,p,pIndex+2)//星号前面的字符出现零次
|| isMatchCore(s,sIndex+1,p,pIndex+2)//出现一次
|| isMatchCore(s,sIndex+1,p,pIndex);//出现两次
}else {
// 表示当前字符不匹配,模式后移两位,字符串不动
// 字符串不动是因为字符串中的当前位置还没匹配成功。
return isMatchCore(s,sIndex,p,pIndex+2);
}
}
// 模式的下一个字符不是“*”,则直接比较字符串和模式中当前位置的字符
if (sIndex<s.length() && (s.charAt(sIndex)==p.charAt(pIndex)
|| p.charAt(pIndex)=='.')) {
return isMatchCore(s,sIndex+1,p,pIndex+1);
}
// 如果当前字符还不相等,则可以判断肯定是不匹配的
return false;
}
}
方法二:动态规划
递归的方法用时很长,主要原因是涉及到模式中下一个字符是星号时,要分三种情况进行递归。
dp数组的含义:dp[i][j]
表示字符串中前i
个字符和字符模式中前j
个字符是否匹配,注意这里的i
和j
指的是长度。
初始条件:
dp[0][0]=true
表示空串和空模式是匹配的。dp[i][0]=false(i!=0)
表示非空串和空模式一定是不匹配的,初始化dp布尔型数组时默认即可。
假设s中的当前字符是长度为i
位置处的字符,p中的当前字符是长度为j
位置处的字符,则状态转移方程:
- 如果p中的当前字符不是星号,则只要比较s中的当前字符和p中的当前字符是否匹配即可,匹配条件可以是字符相等,也可以是p中的当前字符为
“.”
。如果匹配,只需要看s和p当前位置之前的字符串是否匹配即可,即dp[i][j] = dp[i-1][j-1]
- 如果p中的当前字符是星号,星号表示前面的字符出现0次,1次或多次:
- 出现0次,也就是无法匹配,此时
dp[i][j] = dp[i][j-2]
- 出现1次或多次(其实1次是多次的特例,所以放一起了),但是出现并不表示匹配,只有p中当前位置的前一个字符(即长度为
j-1
位置处)和s中当前位置处字符匹配,才会进行状态转移,即dp[i][j] = dp[i-1][j]
- 出现0次,也就是无法匹配,此时
注意:代码中要时刻注意边界的判断。对于p中的当前字符是星号的两种情况代码是用|=
,而不是用赋值符号,因为这两种情况是并列的。
代码如下:
class Solution {
public boolean isMatch(String s, String p) {
if (s == null || p == null) {
return false;
}
int m = s.length();
int n = p.length();
boolean[][] dp = new boolean[m+1][n+1];
// 初始条件
dp[0][0] = true;
for (int i = 0; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (p.charAt(j-1) != '*') {
if (i > 0 && (s.charAt(i-1) == p.charAt(j-1)
|| p.charAt(j-1)=='.')) {
dp[i][j] = dp[i-1][j-1];
}
}else {
// p中的当前字符等于星号,分两种情况讨论:
// 匹配0次
if (j>=2) {
dp[i][j] |= dp[i][j-2];// 注意两种情况用 |= ,而不是直接赋值
}
// 匹配1次或多次,前提是星号前面的字符必须要是匹配的
if (i>0 && j>1 && (s.charAt(i-1) == p.charAt(j-2)
|| p.charAt(j-2)=='.')) {
dp[i][j] |= dp[i-1][j];
}
}
}
}
return dp[m][n];
}
}