持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情
题目描述
题目释义
给定2个字符串
- s代表需要匹配的字符串
- p代表正则表达式
- 规定两个匹配符号,'.'代表任意字符,'*'代表匹配0或n个前面的字符
解题思路
题目需要求出s是否满足p的匹配条件,其实就是我们常用的正则匹配。整个s的匹配度可以拆成无数个字符的匹配,所以很容易想到动态规划的解题思路。
即规定一个dp[i][j],i是s的字符索引,j是p的字符索引,判断s字符的(0,i)和p字符串的(0,j)是否满足正则条件,所以dp[i][j]的值是布尔值。 所以接下来就是寻找dp[i][j]的关系表达式。
p的字符有三种情况
- . dp[i][j] = dp[i-1][j-1];
即当p[j] = '.'时,s[i]=p[j],则dp[i][j]的值取决于它们之前位置的字符的匹配度,所以i和j的值都应该前移一位,即当前dp[i][j]=dp[i-1][j-1];
- 字符 如果s[i] = p[j],则dp[i][j] = dp[i-1][j-1];
如果s[i] ≠ p[j],则dp[i][j] = false;
综上,其实 . 的匹配可以和s[i]=p[j]的情况合并。
- * 因为'*'号是匹配前一个字符,所以这里匹配p的具体字符位置应该是j-1。
-
如果s[i] = p[j-1],dp[i][j] = dp[i-1][j] || dp[i][j-2];
*匹配0或n个,所以这里有两种情况,一种是一个都不匹配,直接匹配前面的,即dp[i][j-2];一种是匹配n个,即当前j不动,i向前移动1位,即dp[i-1][j]。
-
如果s[i] ≠ p[j-1],则dp[i][j] = dp[i-1][j-2];
这里需要注意和普通字符匹配的区别,因为*是匹配0或n个字符,所以它允许匹配不到*号前面的字符,如果s[i] ≠ p[j-1],那么p的字符则需要向前推2个位置,相当于是跳过*号和前面的字符,匹配它们前面的字符。
代码实现
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种情况
if (matches(s,p,i,j-1)){
//s的当前字符和p的前面一个字符相等,比较s的下一个字符是否等于p的前一个字符
dp[i][j] = dp[i][j-2] || dp[i-1][j];
}else {
//s的当前字符和p的前面一个字符不相等,则直接舍弃掉这个表达式
dp[i][j] = dp[i][j-2];
}
}else {
//p[j]是字符
if (matches(s,p,i,j)){
//如果s[i] = p[j]
dp[i][j] = dp[i-1][j-1];
}
}
}
}
return dp[m][n];
}
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);
}