目录
一、题目
请实现一个函数用来匹配包含'. '
和'*'
的正则表达式。模式中的字符'.'
表示任意一个字符,而'*'
表示它前面的字符可以出现任意次(含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" 可
示例 3: 输入: s = "ab" p = ".*" 输出: true 解释: ".*" 表示可匹配零个或多个('*')任意字符('.')。
s
可能为空,且只包含从a-z
的小写字母。p
可能为空,且只包含从a-z
的小写字母以及字符.
和*
,无连续的'*'
。
二、分析
定义dp[i][j]表示字字符串s的前i个字符和p的前j个字符是否匹配。false/ture
dp[i][j]是flase还是ture<==>s[i] vs p[j]<==>s[i-1] vs p[j-1];
字符串p[j]可能是: a~z的普通字符、"."、"*" 三种字符,分别进行分析。
一、确定状态转移关系:
1、p[j]=普通字符:
1.1、s[i]=p[j]:dp[i][j]=dp[i-1][j-1];
1.2、 s[i]!=p[j]: dp[i][j]=flase;
2、p[j]=".",此时p[j]是万能符 :dp[i][j]=dp[i-1][j-1];
3、p[j]="*",因为"*"表示p的第j个字符等于第j-1个字符,所以需要考虑p[j-1]是什么字符:
(1)p[j-1] !=s[i]:相当于把p[j]剔除 dp[i][j]=dp[i][j-2];
(2)p[j-1]=s[i]:此时根据"*"可匹配的字符个数进行讨论
2.1、"*"匹配0个:dp[i][j]=dp[i][j-2];
2.2、"*"匹配1个:相当于删除p的第j自身进行比较==>dp[i][j]=dp[i][j-1];
2.3、"*"匹配n个:相当于删除s的第i个进行比较==>dp[i][j]=dp[i-1][j];
二、选择初始值:
为了让dp[i][j]不越界
遍历时是从i开始遍历,递减:所以需要枚举dp[0][j]可能出现的情况:
dp[0][0]=true;
dp[0][1]=false;
dp[0][j]:j>1需要考虑第二个字符是否为"*";
p[j]=*,相当于要删除两个:dp[0][j]=dp[0][j-2];
p[j]!="*":dp[0][j]=flase;
三、代码
class Solution {
public boolean isMatch(String s, String p) {
// 边界约束
if(s==null||p==null){
return true;
}
int n = s.length();
int m = p.length();
boolean[][] dp= new boolean[n+1][m+1];
// 把可能为true的情况找出来
dp[0][0] = true;
for (int j = 2; j <= m; j++) {
if(p.charAt(j-1)=='*'){
// 相当于消除前面两个
dp[0][j] = dp[0][j-2];
}
}
for (int i = 1; i <= n ; i++) {
for (int j = 1; j <= m; j++) {
// j不为*
if(p.charAt(j-1)!='*'){
if(p.charAt(j-1)=='.'||p.charAt(j-1) == s.charAt(i-1)){
dp[i][j]=dp[i-1][j-1];
}
}else {
// 第j-1个字符不匹配
if(p.charAt(j-2)!=s.charAt(i-1)&&p.charAt(j-2)!='.'){
dp[i][j] = dp[i][j-2];
}else {
// 匹配0/1/n
dp[i][j] = dp[i][j-2]||dp[i][j-1]||dp[i-1][j];
}
}
}
}
return dp[n][m];
}
}
运行结果:
四、总结
动态规划核心是使用数组对历史记录进行保存从而避免重复的计算、
可分为三步进行处理:
1、对dp[i][j]的定义。
2、分析确定状态转移方程。
3、确定dp[0][0]的初始值。