题目
给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。
‘.’ 匹配任意单个字符
‘*’ 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。
说明:
s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母,以及字符 . 和 *。
示例 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
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/regular-expression-matching*
解题
题目解析
该题目的意思是,s是一个只包含小写字母的字符串,p是一个既包含小写字母又含有两个正则符号(’ . ’ 和 ’ * ‘)的字符串。判断s和p是否能匹配,就是p中正则符号替换成对应字母后是否能和s完全相同或者包含与s完全相同的子字符串。(s和p可以为空)
匹配的关键除了字母的相同,主要在于p中两个正则运算符的替换/匹配。
’ . ’ :匹配任意单个字符,表示p中出现的’ . ’ 可以被替换成任意一个小写字母。
’ * ’ :匹配零个或多个前面的那一个元素,表示p中出现的’ * ’ ,可以替换成0个或多个其前面的那个字符,比如a*,可以替换成a或aa或aaa或aaaaa…(n个a)。
思路
采用动态规划的思路
- dp状态:dp[i][j] ,表示 s 中前 i 个字符和 p 中前 j 个字符是否能匹配(注意:这里表示的第i个字符之前和第j个字符之前,对应字符串的下标是i-1和j-1)
- 边界条件:dp[0][0] = true; 表示s和p前0个字符是匹配的,就是两个空字符串是匹配的。
- 状态转移方程:(从前往后递推,不同的状态转移方式主要取决于p中的正则符号的不同)分为两种情况:p中当前字符串为’ * ’ 时与当前字符串为字母时(因为如果当前为’ . ‘则可以匹配任意的字母所以肯定为true)
① 当p遍历到第j个字符时,p[j] = ’ * ’ ,要判断 ‘ * ’ 前面的字符p[j-1]与当前i是否匹配因为 ‘ * ’ 要考虑替换成它前一个字符是否能匹配。
d p [ i ] [ j ] = { d p [ i − 1 ] d p [ j ] o r d p [ i ] [ j − 2 ] , s[i-1] == p[j-2] 即matches(s,i,p,j-1) d p [ i ] [ j − 2 ] , 其他 dp[i][j] = \begin{cases} dp[i-1]dp[j]\ or\ dp[i][j-2],\text{s[i-1] == p[j-2] 即matches(s,i,p,j-1)}\\ dp[i][j-2],\text{其他} \end{cases} dp[i][j]={dp[i−1]dp[j] or dp[i][j−2],s[i-1] == p[j-2] 即matches(s,i,p,j-1)dp[i][j−2],其他
② 当p遍历到第j个字符时,p[j] 不是’ * '时 ,直接判断p中当前j和s中当前i是否相等即可转换为dp[i-1][j-1]:
d p [ i ] [ j ] = { d p [ i − 1 ] d p [ j − 1 ] , s[i-1] == p[j-1]即matches(s,i,p,j) f a l s e , 其他即当前i和j字符不相等的情况 dp[i][j] = \begin{cases} dp[i-1]dp[j-1],\text{s[i-1] == p[j-1]即matches(s,i,p,j)}\\ false,\text{其他即当前i和j字符不相等的情况} \end{cases} dp[i][j]={dp[i−1]dp[j−1],s[i-1] == p[j-1]即matches(s,i,p,j)false,其他即当前i和j字符不相等的情况
具体见代码注释
java实现
class Solution {
public boolean isMatch(String s, String p) {
//动态规划
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) == '*'){//p的第一个不可能为'*',所以可以有j-2 dp[j]对应的字符下标是j-1
//如果p中第j个字符为*时,ij匹配可以时将当前*和其前面一个字符视为整体
//① p中第j个数为*,比较s中第i个数和p中第j-1个数,如果能匹配上说明j也可以和j-1一样和i匹配上,即s[i-1]==p[j-2]==p[j-1],那么dp[i][j] = dp[i-1][j-2];因为*表示可以匹配多个,所以不能单纯的转移到dp[i-1][j-2],可以继续比较第i-1个数和j-1个数,所以要转移到dp[i-1][j]
//② 因为*可以匹配零个元素前面的元素,也就是可以把*和前面的字符消掉,dp[i][j] = dp[i][j-2];
//以上两种情况其中有一个true就可以,所以用||
//②
if(matches(s,p,i,j-1)){
dp[i][j] = dp[i-1][j];
}
dp[i][j] = dp[i][j] || dp[i][j-2];
}
//p中第j个字符不为*
else{
//s中第i个和p中第j个字符相等
if(matches(s,p,i,j)){
dp[i][j] = dp[i-1][j-1];
}
}
}
}
return dp[m][n];
}
//s中第i个字符和p中第j个字符是否能匹配所以要比s[i-1]和j[i-1]
public boolean matches(String s, String p, int i, int j){
if(i == 0){
return false;
}
if(p.charAt(j-1) == '.'){
return true;
}
return s.charAt(i-1) == p.charAt(j-1);
}
}
参考:力扣官方题解