题目
给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。
'.' 匹配任意单个字符
'*' 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。
示例 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)
链接:leetcode-cn.com/problems/re…
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
结题思路
该题目可以使用动态规划来进行求解。
状态矩阵
使用dp[i][j]
来表示s[i]
是否与p[j]
能够匹配,能则为true
,否则为false
我们从字符串s
和匹配串p
的末尾往前看:
- 1. 匹配串
p
的当前字符为字母,或者点- 字符串
s
应该有一个对应的字母来进行比较 - 如果匹配了,问题就等价于字符串
s
去除最后一个字母,匹配串p
去除最后一个字母或者点 - 如果不匹配,就说明字符串不匹配
- 字符串
红色分别为当前p
是字母和点的两种情况,此时s
的当前字母与p
当前值匹配,因此问题可以转化为绿色的情况。
- 2. 匹配串
p
的当前字符为*
- 要将
*
前面的字符或者点与字符串s
的当前值进行比较 - 2.1 如果不匹配,不能认为匹配失败,因为
字母*
可以出现0次,所以要略过匹配串中的该字母*
,字符串s
不动,匹配串p
的索引前进两位 - 2.2 如果
字母*
匹配,会有两种情况,要将两种情况取或运算的结果:- 2.2.1
字母*
中的字母出现在字符串s
中次数为0,此时应该略过匹配串中的字母*
然后继续比较 - 2.2.2
字母*
中的字母出现在字符串s
中次数大于0,此时应该将字符串s
索引前移一位,匹配串不动,看该字母是否继续会出现
- 2.2.1
- 要将
疑问:
这里有一个很关键的问题,处于2.2.1
情况,即当匹配串当前为*
时,比较前面的字母,如果匹配了,为什么还会有该字母在字符串s
中出现次数为0的情况?
原因是虽然当前匹配了,但是有可能当前字符传的字母并不是与匹配串p
中的字母*
匹配(即字母*
出现0次),而是与p
中其他的字母
或者点
进行了匹配。
该情况后续会举例说明,现在看不懂也没有关系。
上图第一行是2.1
的情况,即当前为*
,前置字母不匹配,因为可能有c*
不会出现的情况,所以j
前移两位,转换成绿色的情况。
上图第二行左半部是2.2.2
的情况,对应公式中红色部分,即当前为*
,前置字母匹配,也确实在s
中出现了一次,此时应该将i
前移一位,继续看a*
是否还会出现。
上图第二行右半部是2.2.1
的情况,对应公式中蓝色部分,即当前为*
,前置字母匹配,但是字母a
在s
中出现次数为0,应该将j
左移两位,可能有人会问,a
不是在s
中出现了么,怎么说没有出现?,这是因为s
中出现的a
,对应的是p
中数组下标为0
时的a
,即图中红色的两个a
是对应的,蓝色的a*
其实并没有字母与其对应。
状态转移方程
如何判断是否匹配?
需要注意的点
dp[0][0]
表示的是s
和p
都是空串的时候是否匹配,那如果想要看看s
整个串和p
整个串是否匹配,需要看的是dp[s.length][p.length]
,因此dp
的长度应该是new boolean[s.length+1][p.length+1]
。
填表的时候,行下标i
从0
到s.length
,列下标j
从1
到s.length
,因为表的第0列用不上,所以可以不填写。然后在程序中需要从s
和p
去元素的时候,都应该将循环变量i
和j
进行减1,才是其数组的当前值。
代码
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) == '*') {
dp[i][j] = dp[i][j-2];
if (matches(s, p, i, j-1)) {
dp[i][j] = dp[i-1][j] || dp[i][j];
}
} else {
if (matches(s, p, i, j)) {
dp[i][j] = dp[i-1][j-1];
} else {
dp[i][j] = false;
}
}
}
}
return dp[m][n];
}
private 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);
}
单元测试
public class Solution_Test_10 {
Solution_10 solution_10 = new Solution_10();
@Test
public void test1() {
String s = "aa";
String p = "a";
boolean match = solution_10.isMatch(s, p);
assertEquals(match, false);
}
@Test
public void test2() {
String s = "aa";
String p = "a*";
boolean match = solution_10.isMatch(s, p);
assertEquals(match, true);
}
@Test
public void test3() {
String s = "ab";
String p = ".*";
boolean match = solution_10.isMatch(s, p);
assertEquals(match, true);
}
@Test
public void test4() {
String s = "aab";
String p = "c*a*b";
boolean match = solution_10.isMatch(s, p);
assertEquals(match, true);
}
@Test
public void test5() {
String s = "mississippi";
String p = "mis*is*p*.";
boolean match = solution_10.isMatch(s, p);
assertEquals(match, false);
}
@Test
public void test6() {
String s = "aaa";
String p = "ab*a*c*a";
boolean match = solution_10.isMatch(s, p);
assertEquals(match, true);
}
}