
时间复杂度:O(n²)
解题思路
二维动态规划。
令dp[i][j]表示字符串前i个字符与正则表达式的前j个字符能否匹配成功,成功为true,否则为false。所以根据定义可以确定边界条件dp[i][j]=true,因为任何一个空字符串都能和空正则表达式匹配上。
对于正常的正则表达式字符,我们只需要dp[i-1][j-1]的状态,因为如果字符串第i个字符和正则表达式第j个字符匹配成功,那么如果字符串第i-1个字符和正则表达式第j-1个字符也匹配成功即dp[i-1][j-1]=true,那么dp[i][j]也为true;否则即使字符串第i个字符和正则表达式第j个字符匹配成功,但由于前面的字符匹配失败即dp[i-1][j-1]=false,那么dp[i][j]也是false。那如果字符串第i个字符和正则表达式第j个字符匹配失败了呢?那dp[i][j]肯定是false呀。
但如果当前的正则表达式字符是*呢?
既然有*,那么我们就可以试着不匹配*前的字符,即让*前的字符出现0次,对应的状态转移方程就是dp[i][j]=dp[i][j-2]。对于该式可以这么理解,如果不考虑*前的字符和*也可以让前i个字符匹配成功的话,那么就让*前的字符出现0次,这样前i个字符不就可以匹配上前j个正则表达式字符了嘛。
那如果让*前的字符出现至少1次呢,状态转移方程是什么样子的?
如果*前的字符至少出现1次,那么就尝试匹配字符串第i个字符和*前的字符。如果二者匹配成功,那么就让dp[i][j]=dp[i][j]||dp[i-1][j],意思是如果前i-1个字符能和前j个正则表达式字符匹配成功,那么现在又因为第i个字符和*前的字符匹配成功,所以dp[i][j]还是为true,加上或的原因是可能dp[i][j]已经为true了;如果二者没匹配成功,那就很遗憾了,*前的字符根本不可能出现至少1次,否则就匹配不上了。
AC代码
看完官方题解的代码后,自己根据自己的理解写的,基本与题解代码一致,但是有几处略有不同。
func isMatch(s string, p string) bool {
sLen,pLen:=len(s),len(p)
match:=func(i,j int)bool{
if i==0{//边界条件,s为空字符串那么一定不会和正则表达式某个字符匹配
return false
}
if p[j-1]=='.'{//任意一个字符都能和.匹配成功
return true
}
return s[i-1]==p[j-1]//比较字符串的字符和正则表达式的字符是否相同,相同就匹配成功
}
dp:=make([][]bool,sLen+1)
for i:=0;i<len(dp);i++{
dp[i]=make([]bool,pLen+1)
}
dp[0][0]=true//边界条件,空字符串和空正则表达式一定匹配成功
for i:=0;i<=sLen;i++{
for j:=1;j<=pLen;j++{
if p[j-1]=='*'{
dp[i][j]=dp[i][j-2]//不匹配*前的字符,也就是取0个
if match(i,j-1){//字符串第i个字符可以和*前的字符匹配
dp[i][j]=dp[i][j]||dp[i-1][j]//如果字符串的第i个字符的前一个字符也能与*前的字符匹配,那么由于第i个字符也与*前的字符匹配成功,所以dp[i][j]也是true,相当于匹配了1个及以上*前字符
}
}else if match(i,j){//不是*,直接看能不能匹配两个字符
dp[i][j]=dp[i-1][j-1]//如果匹配成功,并且前个字符也匹配成功,那么dp[i][j]就是true
}
//既不是*也没匹配成功,默认就是false
}
}
return dp[sLen][pLen]
}
感悟
看题解的文字看了半小时才看明白,看题解代码又看了半小时才彻底看懂,不过自己可以很流畅地写出AC代码。其实状态量的改变只需考虑三个:①dp[i-1][j-1]:对应于非*的正则表达式字符的正常匹配情况;②dp[i][j-2]:正则表达式字符是*,且让*前的字符出现0次;③dp[i-1][j]:让*前的字符出现至少1次。

本文围绕正则表达式匹配问题,运用二维动态规划算法解题。定义dp[i][j]表示匹配状态,确定边界条件。针对正常字符和特殊字符“*”分别给出状态转移方程。作者参考官方题解写出AC代码,指出状态量改变主要考虑三种情况。
442

被折叠的 条评论
为什么被折叠?



