描述
请实现一个函数用来匹配包括'.'和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但是与"aa.a"和"ab*a"均不匹配’
正则表达式(Regular Expression)是一种文本模式,包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为"元字符")。
一般利用正则表达式进行字符串操作。
正则表达式教程—https://www.runoob.com/regexp/regexp-tutorial.html
来自官方的详细题解,虽然之前的JZ49有看到正则表达式,但是关于正则表达式的匹配问题还是较难理解。
首先是递归解题思路。
先来看一下pattern长度只有一位的情况下的匹配:
True | Pattern | ||
0(索引下标) | |||
a | |||
Str | 0(索引下标) | a | 匹配 |
True | Pattern | ||
0(索引下标) | |||
. | |||
Str | 0(索引下标) | a | 匹配 |
False | Pattern | ||
0(索引下标) | |||
b | |||
Str | 0(索引下标) | a | 不匹配 |
所以递归的终止条件就是str[0] = pattern[0](记为first_match)。
然后继续看pattern长度为两位的时候,并且pattern[1]=’*’时,结合三个示例看:
False | Pattern | |||
0(索引下标) | 1 | |||
b | * | |||
Str | 0(索引下标) | a | 不匹配 | |
1 | a | 匹配 |
True | Pattern | |||
0(索引下标) | 1 | |||
a | * | |||
Str | 0(索引下标) | a | 匹配 | |
1 | a | 匹配 |
True | Pattern | |||
0(索引下标) | 1 | |||
. | * | |||
Str | 0(索引下标) | a | 匹配 | |
1 | b | 匹配 |
综上,可以看到只有上文提到的str[0] = pattern[0],也就是first_match满足时,并且str[1]=pattern[1]时,正则匹配为True。
保持pattern[1]=’*’不变,来看一下len(pattern)>2的情况:
False | Pattern | ||||
0(索引下标) | 1 | 2 | |||
a | * | b | |||
Str | 0(索引下标) | a | 匹配 | ||
1 | a | 匹配 | |||
2 | a | 不匹配 |
False | Pattern | ||||
0(索引下标) | 1 | 2 | |||
a | * | a | |||
Str | 0(索引下标) | b | 不匹配 | ||
1 | a | 匹配 | |||
2 | a | 匹配 |
True | Pattern | ||||
0(索引下标) | 1 | 2 | |||
. | * | a | |||
Str | 0(索引下标) | a | 匹配 | ||
1 | a | 匹配 | |||
2 | a | 匹配 |
综上,所需要的条件仍然是str[0] = pattern[0],并且str[1:] = pattern才成立(因为上面那种情况是len(pattern)=2,这里长度变了,所以后面需要匹配的条件也变了)。
但是所有情况都需要str[0] = pattern[0]吗?作者还有另外一种示例:
False | Pattern | ||||||
0(索引下标) | 1 | 2 | 3 | 4 | |||
c | * | a | * | b | |||
Str | 0(索引下标) | a | 不匹配 | ||||
1 | a | 匹配 | |||||
2 | b | 不匹配 | |||||
3 | |||||||
4 |
True | Pattern | ||||||
0(索引下标) | 1 | 2 | 3 | 4 | |||
c | * | a | * | b | |||
Str | 0(索引下标) | ||||||
1 | |||||||
2 | a | 匹配 | |||||
3 | a | 匹配 | |||||
4 | b | 匹配 |
所以另外一种也可以成功匹配的方法就不局限于str[0] = pattern[0]了。
刚刚看到了pattern[1]=’*’的情况,再来看patterb[1]<>’*’,意思是星号在索引1之后的任意位置,因为索引1之后,所以len(pattern)>2。
False | Pattern | |||||
0(索引下标) | 1 | 2 | 3 | |||
a | b | * | a | |||
Str | 0(索引下标) | a | 匹配 | |||
1 | a | 不匹配 | ||||
2 | a | 匹配 |
True | Pattern | ||||||||
0 | 1 | 2 | |||||||
0(索引下标) | 1 | 2 | 3 | 4 | 5 | 6 | |||
a | b | * | a | c | * | a | |||
Str | 0(索引下标) | a | 匹配 | ||||||
1 | a | 匹配 | |||||||
2 | a | 匹配 |
(另外https://blog.nowcoder.net/n/ca8faf039491463b97e3a2e5bda0bfed?f=comment)这里需要知道一些补充知识:如果str[0]=pattern[0],可以有3种匹配方式:
- pattern后移2字符,相当于x*被忽略(也就是为什么上面这个表格原本的1,2,4,5列被忽略的原因)
- str后移1字符,pattern后移2字符; 相当于x*算一次
- str后移1字符,pettern不变,即继续匹配str下一位,因为*可以匹配多位,相当于算多次
class Solution:
def match(self , str , pattern ):
if not pattern: #1.特殊情况,不存在匹配模式,那么就没有匹配字符串
return not str
#2. 递归的终止条件f(1) = 1:在这里就是 首位即匹配。
first_match = str and pattern[0] in {str[0], '.'}
#如果模式长度 >= 2,并且 模式索引[1] == '*'情况,也要分两种:
if len(pattern) >= 2 and pattern[1] == '*':
#第一种就是模式长度>2的情况下:字符串完全匹配从模式索引2之后的位置
return (self.match(str, pattern[2:]) or
#或者模式长度 =2的情况下:字符串从索引1位置开始,完全匹配模式
first_match and self.match(str[1:], pattern))
else:
#否则,模式长度>=2,而模式索引从1开始是 点字符或 *字符在其他位置,
return first_match and self.match(str[1:], pattern[1:])
还有另外一种动态规划的解法:
动态规划其实在剑指里面用的很多,包括前面的斐波那契数列等
来自同一个作者的题解:
- 定义一个f[i][j]的状态转移方程,其中i 表示str中的第i个字符;j表示pattern中的第j个字符,然后判断是否匹配。
- 接着我们需要判断两种情况,第一种是当i、j指向的字符是同一个字母/点号(".")的时候,我们只需要判断对应位置的字符是否相同,相同则转移状态去判断子问题f[i-1][j-1]是否匹配。
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
#
#
# @param str string字符串
# @param pattern string字符串
# @return bool布尔型
#
class Solution:
def match(self , str , pattern ):
m, n = len(str), len(pattern) # 分别找到str和pattern的长度
def matches(i, j): #定义一个转移方程函数
if i == 0: #首先考虑一种特殊情况: str为空;
#否则第一种是当i、j指向的字符是同一个字母/点号(".")的时候,我们只需要判断对应位置的字符是否相同,
#相同则转移状态去判断子问题f[i-1][j-1]是否匹配
return False
if pattern[j - 1] == '.':
return True
return str[i - 1] == pattern[j - 1]
f = [[False] * (n + 1) for _ in range(m + 1)]
f[0][0] = True #动态规划的边界条件为f[0][0]=true,即两个空字符串是可以匹配的
for i in range(m + 1):
for j in range(1, n + 1):
#判断当j 指向 * 号的时候两种情况:
if pattern[j - 1] == '*':
f[i][j] |= f[i][j - 2]
if matches(i, j - 1):
f[i][j] |= f[i - 1][j]
else:
if matches(i, j):
f[i][j] |= f[i - 1][j - 1]
return f[m][n]