导读:算法哥前面分享了一个《通配符匹配》,有粉丝留言,算法哥你再讲讲leetcode上另一道《正则表达式匹配》,正则表达式匹配这道题是前面通配符匹配的加强版,大家一起来学习吧!
题目描述
给定一个字符串 (s) 和一个字符模式 (p)。实现支持 '.' 和 '*' 的正则表达式匹配。
'.' 匹配任意单个字符。 '*' 匹配零个或多个前面的元素。
匹配应该覆盖整个字符串 (s) ,而不是部分字符串。
题目来源:LeetCode 10:Regular Expression Matching
说明:
- 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
题目分析
这道题目已经有正则表达式的意思在里面了,.表示任意字符,*表示零个或多个前面的元素。这道题目和突破LeetCode,拿BAT大厂offer之《通配符匹配》(动态规划)的区别在于,前者的*只是表示任意字符串,后者表示零个或者多个前面的元素。
我们还是和前面一道题同样的思路,令dp[i][j]表示字符串s前i个字符和模式串p的前j个字符的匹配情况,匹配等于1,不匹配等于0!
那么我们怎么根据dp数组记录的状态进行状态转移呢?也就是说dp[i][j]可以由哪些状态递推过来呢?我们一起来看图吧!
情形1:
显然绿色位置已经匹配,所以dp[1][1]=1,我们考察红色的位置,也就是dp[2][2],此时因为s[2]=p[2],且dp[1][1]=1,所以显然dp[2][2]=1;
情形2:
绿色部分表示已经匹配了,此时dp[10][9]=1,此时p[10]=.,s[11]=i,因为.匹配任意字符,所以dp[11][10] = 1;
情形3:
大家注意到图片里新增加了下标0,目的是说明dp[0][0]=1,且dp[0][2]等于什么呢?,哈哈此时p[2]=*,重复0次,是不是dp[0][2]=1!注意这个情形哦,不然样例4就解释不过了!
情形4:
此时dp[1][3]=1,那dp[1][4]等于什么呢?因为p[4]=*,重复一次,dp[1][[4]=1.
总结一下:
- 情形1和情形2对应了由dp[i-1][j-1]推导dp[i][j];
- 情形3对应了由dp[i][j-2]推导dp[i][j];
- 情形4对应了由dp[i][j-1]推导dp[i][j];
聪明的读者肯定注意到了那dp[i-1][j]推导dp[i][j]呢?这个就留给读者自己思考吧,读者也可以对照算法哥给出的源码自己画图推导!
上源码:
复杂度分析
两层for循环,时间复杂度是O(mn)的,m表示字符串s的长度,n表示模式串p的长度;空间复杂度是O(mn)的,这里依然可以用滚动数组优化到O(n),不会的同学就翻算法哥以前的文章吧!
题目总结
这个题目比前面的《通配符匹配》难度上要大一点,状态转移如果不能抓住问题的本质,还是有一定难度的,通过这个题目,相信大家对算法哥说的大问题转化为小问题,小问题推导大问题又有了新的认识吧!
题目分享完毕,请帮助算法哥上头条吧,您的关注,转发,点赞,评论,都是对算法哥最大的鼓励!