1. 问题:
给定一个字符串s和一个字符模式p,实现支持 "." 和 "*"的正则表达式匹配。
"." 匹配任意单个字符, "*" 匹配0个或者多个前面的元素。匹配应该覆盖整个字符串s,而不是部分字符串。
说明:
s
可能为空,且只包含从a-z
的小写字母。p
可能为空,且只包含从a-z
的小写字母,以及字符.
和*
。
示例 1:
输入:
s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。
示例 2:
输入:
s = "aa"
p = "a*"
输出: true
解释: '*' 代表可匹配零个或多个前面的元素, 即可以匹配 'a' 。因此, 重复 'a' 一次, 字符串可变为 "aa"。
示例 3:
输入:
s = "ab"
p = ".*"
输出: true
解释: ".*" 表示可匹配零个或多个('*')任意字符('.')。
示例 4:
输入:
s = "aab"
p = "c*a*b"
输出: true
解释: 'c' 可以不被重复, 'a' 可以被重复一次。因此可以匹配字符串 "aab"。
示例 5:
输入:
s = "mississippi"
p = "mis*is*p*."
输出: false
2. 问题分析:
动态规划的思路:找到一个递推公式,由前向后或者由后向前求解题目。
在字符串匹配问题中,基本思想是:从前往后,维护两个指针(一个指针遍历s,一个指针遍历p,并不断判断当前两指针的子串是否匹配),这种思想可以用一个二维布尔表dp来实现。
二维表格dp的大小为(len(s)+1)*(len(p)+1),dp[i][j]表示,若s的子串[0,i)和p的子串[0,j)匹配,dp[i][j]=True ,若不匹配dp[i][j]=False。最后返回的就是右下角位置的数字。
2.1 首先完善dp表的第一行和第一列。
当 p 为空串时,只有目标串 s 的空串才能与 p 匹配;
当 s 为空串时,只有 p 为 空串 或者 为 x*y* 的形式才能与 s 匹配。
#2.1 初始化dp表,初始化表的第一列和第一行
dp = [[False for j in range(len(p)+1)] for i in range(len(s)+1)] # 初始化dp表
dp[0][0] = True # s和p 都为空时匹配
# s 为空串时
for j in range(1, len(p)+1): # [j-1]为p的真实索引
dp[0][j] = (j>=2) and (p[j-1]=="*") and dp[0][j-2] # 只有x*能匹配空串,若有*,它的真值一定和dp[0][j-2]的相同
# 注意:and的语句可以转换成if判断语句,如: dp[0][j] = (j>=2) and (p[j-1]=="*") and dp[0][j-2],可转化为如下if语句:
#if p[i-1]=='*':
#if i>=2:
#dp[0][i]=dp[0][i-2]
2.2 然后两层循环填充剩下的部分。
每次s字符串往下走一个字符,和所有的p子串进行匹配,接下来分两种情况进行分类。
( a ) 假设当前位置为dp[i][j],若p[j-1] == "*"时,"*"的用法分为两种(1:代表空串 2:代表一个或者多个前一个字符)
要想dp[i][j] =1,需要满足下列条件中的任一个:
(a.1)dp[i][j-2] =1时,此时"*"代表空串
(a.2)dp[i-1][j] =1时且满足(p[j-2]==s[i-1] or p[j-2]=="."),此时"*"代表对前一字符的复制
( b ) 若p[j-1]!= "*"时,要想dp[i][j] =1,需满足(p[j-1]==s[i-1] or p[j-1]=="."),且还要判断前面的是否匹配,即dp[i-1][j-1]的值是否为True。
3. 图解例子:
假设: s="aaaa" p="a*b*" 求两者是否匹配,做下图所示:
红色的箭头: p[j-1] != "*"时,
绿色的箭头:p[j-1] == "*"时, "*" 代表空串
黄色的箭头:p[j-1] == "*"时, "*" 代表一个或者多个前面的元素
4. 程序:
class Solution:
def isMatch(self, s, p):
dp=[[False for j in range(len(p)+1)] for i in range(len(s)+1)] # 初始化二维表dp
print(dp)
dp[0][0]=True # s 和 p 都为空时
# 若 s 为空时
for j in range(1,len(p)+1):
#dp[0][j]= (p[j-1]=="*")and(j>=2)and(dp[0][j-2]) # 等同于下列语句
if p[j-1]=='*':
if j>=2:
dp[0][j]=dp[0][j-2]
print(dp)
for i in range(1,len(s)+1):
for j in range(1,len(p)+1):
# j-1才为正常字符串中的索引
# p当前位置为"*"时,(代表空串--dp[i][j-2]、一个或者多个前一个字符--( dp[i-1][j] and (p[j-2]==s[i-1] or p[j-2]=='.'))
if p[j-1]=='*':
dp[i][j]= dp[i][j-2] or ( dp[i-1][j] and (p[j-2]==s[i-1] or p[j-2]=='.') ) # dp[i][j-1] or
# p当前位置为"."时或者与s相同时,传递dp[i-1][j-1]的真值
else:
dp[i][j]=(p[j-1]=='.' or p[j-1]==s[i-1]) and dp[i-1][j-1]
return dp[len(s)][len(p)]
ss = Solution()
s="a"
p=".*"
print(ss.isMatch(s,p))
参考: