刷题使我快乐,满脸开心.jpg
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/regular-expression-matching/
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
题目
给你一个字符串 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
解释:".*" 表示可匹配零个或多个('*')任意字符('.')。
提示:
1 <= s.length <= 20
1 <= p.length <= 20
s
只包含从a-z
的小写字母。p
只包含从a-z
的小写字母,以及字符.
和*
。- 保证每次出现字符
*
时,前面都匹配到有效的字符
思路
这道困难题,感觉难度一小半在确定动规思路,一小半在动规状态转移方程
动规思路
正则匹配我们其实都熟悉,都会注意到几个点
- 遇到
'.'
则可以匹配任意字符,那么就需要关注p
字符串当前判断字符是否为'.'
- 遇到
'*'
则可以将前一个字符从0
到无限
重复,所以需要关注p
字符串前一个字符
- 如果
'.'
和'*'
相邻,则可以匹配所有字符,那么还需要关注之后两个字符串的尾巴
是否匹配
这些思路如果要直接用双指针
之类的方式实现,其实要考虑的边界等情况会很繁复,不敢说不能实现,但是应该会比较复杂
所以其实也是基于这个思路,但是形式上更加清晰的动态规划就更为合适了:
- 后面的结果依赖于前面字符的匹配情况
- 基于上面的思路可以得出状态转移方程
- 无后效性,之前字符串是否匹配的结果,不会影响增加字符后,整体字符串的匹配情况
动规要素
dp
方程定义:dp[i][j]
表示s[:i]
和p[:j]
是否匹配- 初始化:
dp[0][0] = true
即s
和p
均为空串时是匹配的- 因为
'*'
可以取0
个循环,所以当p[i-1]=='*'
时,dp[0][j]=dp[0][j-2]
- 状态转移方程
p[i-1] == '*'
时s
字符串不变,此时p
字符串结尾是'a*'
或者'.*'
,都可以匹配:dp[i][j] = dp[i][j-2]
s
字符串s[i-1]='a'
,同时p[i-2]='a'
,可以匹配:dp[i][j] = dp[i-1][j] && s[i-1] == p[i-2]
s
字符串s[i-1]='a'
,同时p[i-2]='.'
,可以匹配:dp[i][j] = dp[i-1][j] && p[i-2] == '.'
p[i-1] != *
时s
字符串s[i-1]='a'
,同时p[i-2]='a'
或者p[i-2]='.'
,都可以匹配:dp[i][j] = dp[i][j-2]
整体思路就是这样啦,直接上代码,结合注释会更加清晰。
代码
func isMatch(s string, p string) bool {
sLen, pLen := len(s), len(p)
// 定义dp数组 dp[i][j] 表示 s[:i] 和 p[:j] 是否匹配
dp := make([][]bool, sLen+1)
for i := 0; i <= sLen; i++ {
dp[i] = make([]bool, pLen+1)
}
// 初始化
// dp[0][0] 即 s 和 p 均为空串时是否匹配
dp[0][0] = true
// 这个初始化条件,其实就是因为 * 可以取 '0个XXX' 的含义,
// 使得 s=="" && p != "" 也可能是匹配的
for j := 1; j <= pLen; j++ {
if p[j-1] == '*' {
dp[0][j] = dp[0][j-2]
}
}
// 状态转移方程
// p[i-1] == * 时
// s字符串不变,此时p字符串结尾是 'a*' 或者 '.*',都可以匹配: dp[i][j] = dp[i][j-2]
// s字符串 s[i-1]='a',同时 p[i-2]='a',可以匹配: dp[i][j] = dp[i-1][j] && s[i-1] == p[i-2]
// s字符串 s[i-1]='b',同时 p[i-2]='.',可以匹配: dp[i][j] = dp[i-1][j] && p[i-2] == '.'
// p[i-1] != * 时
// s字符串 s[i-1]='a',同时 p[i-2]='a' 或者 p[i-2]='.',都可以匹配: dp[i][j] = dp[i][j-2]
for i := 1; i <= sLen; i++ {
for j := 1; j <= pLen; j++ {
if p[j-1] == '*' {
dp[i][j] = dp[i][j-2] || dp[i-1][j] && (s[i-1] == p[j-2] || p[j-2] == '.')
} else {
dp[i][j] = dp[i-1][j-1] && (p[j-1] == '.' || s[i-1] == p[j-1])
}
}
}
return dp[sLen][pLen]
}