发布于 

Leetcode 10:正则表达式匹配

题目: 正则表达式匹配

难度:Hard

知识点:动态规划、递归

按照1xx、2xx的顺序做题,然后发现10也是一个值得记录一下的题目,于是写一下吧。

首先说明一点的是,leetcode官方给出的答案属实是没看懂,我还是自己写一下吧

终于看懂官方的答案了,并且自己写了出来,详见解法2。

解法1

看到这个题目,第一感觉很难想到官解的动态规划思路,感觉递归才是人之常情,这里就先写一下递归的操作吧,当然,下面的递归算不上是最好的算法,同时可以增加dp消除重复字问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//执行耗时:14 ms,击败了16.27% 的Java用户
//内存消耗:39.8 MB,击败了81.81% 的Java用户
class Solution {
char[] arrs;
char[] arrp;

public boolean isMatch(String s, String p) {
arrs = s.toCharArray();
arrp = p.toCharArray();
return dp(0,0);
}

public boolean dp(int indexS, int indexP) {
int m = arrs.length;
int n = arrp.length;
//base case
if (indexP == n) return indexS == m;
if (indexS == m) {
//判断剩余元素是否为奇数
if ((n - indexP) % 2 == 1) {
//如果剩余元素是奇数,那么直接就不能够完全消除掉
return false;
}
for (; indexP + 1 < n; indexP = indexP + 2) {
if (arrp[indexP + 1] != '*') {
return false;
}
}
return true;
}
//开始递归操作
boolean res = false;
//如果当前位置匹配
if (arrs[indexS] == arrp[indexP] || arrp[indexP] == '.') {
//如果下一位是*,name我们要考虑用不用掉这个*
if (indexP < n - 1 && arrp[indexP + 1] == '*') {
// 这个*代表匹配indexP的元素0次(indexP+1是*,跳过
// 第二种情况代表着匹配多次,也就是说indexS递增,indexP不变
res = dp(indexS,indexP+2) ||
dp(indexS+1,indexP);
}else{
res = dp(indexS+1,indexP+1);
}
} else {
//如果当前位置不匹配,那么就只能消耗掉(字母*)
if (indexP < n - 1 && arrp[indexP + 1] == '*') {
res = dp(indexS, indexP + 2);
} else {
res = false;
}
}
return res;
}
}

解法2

还是需要研究一下官方的解决方法,这里做一个简单的记录吧

几个注意的点:

  • s字符串需要有前0位(空)的情况,因为前0位可以和特殊的前两位(a*)进行匹配,不能忽略递归结构
  • dp中的编号与字符串的index要区分开!!!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//执行耗时:1 ms,击败了99.97% 的Java用户
//内存消耗:39.9 MB,击败了77.69% 的Java用户
class Solution {
char[] arrs;
char[] arrp;
boolean[][] ans;

public boolean isMatch(String s, String p) {
arrs = s.toCharArray();
arrp = p.toCharArray();
ans = new boolean[s.length() + 1][p.length() + 1];
//默认情况应该是dp[0][0] = true;
ans[0][0] = true;
dp();
return ans[arrs.length][arrp.length];
}

public void dp() {
for (int indexS = 0; indexS <= arrs.length; indexS++) {
for (int indexP = 1; indexP <= arrp.length; indexP++) {
if (arrp[indexP - 1] != '*') {
//这一位不是*,这一位可以靠自己
if (check(indexS - 1, indexP - 1)) {
ans[indexS][indexP] = ans[indexS - 1][indexP - 1];
}
} else {
/**
* 第indexP位是*
* arrp[indexP-1]是*
* arrp[indexP-2]是可以被消掉的
* 匹配多次的是arrp[indexP-2]
*/
if (indexP >= 2)
//相当于向前缩了两位,在编号上-2即可
ans[indexS][indexP] = ans[indexS][indexP - 2];
//匹配多次的情况,检查当前字符和*前面的字符是否一致,一致才有下文
if (check(indexS - 1, indexP - 2)) {
//如果一致,还要考虑前一位和当前的匹配关系,前一位可能是匹配0次,也可能是匹配一次,需要推倒回去,这也是很多情况的一个压缩!!!
//①只需要匹配1次:if保证了匹配,递归一次变匹配0次
//②匹配多次,我们削减待匹配的字符串,直到0次
ans[indexS][indexP] = ans[indexS - 1][indexP] || ans[indexS][indexP];
}
}
}
}

}

public boolean check(int indexS, int indexP) {
if (indexS == -1) return false;
if (arrp[indexP] == '.' || arrp[indexP] == arrs[indexS]) {
return true;
}
return false;
}
}

很佩服出题人,也佩服能想到这种解法的人!

解法3

1
2
3
4
5
6
7
8
//执行耗时:33 ms,击败了9.81% 的Java用户 
//内存消耗:42 MB,击败了5.03% 的Java用户
class Solution {

public boolean isMatch(String s, String p) {
return s.matches(p);
}
}

大大的鄙视!