题目:
给你一个字符串 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
的小写字母,以及字符.
和*
。- 保证每次出现字符
*
时,前面都匹配到有效的字符
思考方向:
1、字符串是否匹配取决于字符串每一位的匹配结果,如果前一位字符都不匹配,则后续将不会再匹配了,所以适合动态规划的结题思路,也即:后面的结果可以由前面的结果推导出来
2、因为字符串匹配是两个字符串,所以采用二维的动态规划
3、*匹配零个或多个前面的那一个元素,这个是结题的关键,尤其是匹配零个,这个经常让我们忽略,比如下例:
输入:s = "a", p = "ab*"
输出:True
解释:"b" 后面有*,表示b出现了0次。
动态规划的状态转移方程:
我们用 f[i][j]表示 s的前 i个字符与 p中的前 j 个字符是否能够匹配。
在进行状态转移时,我们考虑 p 的第 j 个字符的匹配情况:
1、如果 p 的第 j 个字符是一个小写字母( '.' 匹配任意一个字母,可以看成一个特殊的字母),那么我们必须在 s 中匹配一个相同的小写字母,若相等,只需判断 i,j 之前的字符串是否匹配即可,也就转化为子问题 f[i-1][j-1],若不等,则当前的 i,j 肯定不能匹配,则f[i][j]= false.即可写出公式:
2、如果 p 的第 j 个字符是 *,那么就表示我们可以对 p 的第 j−1 个字符匹配0次或多次,则不妨把类似 'a*', 'b*' 等的当成整体看待:
2.1、如果p[j-1]与s[i]不匹配,那么f[i][j]的匹配情况需要看 f[i][j-2],也就是a*匹配0次(等于没出现a*)的情况,那么也就转化为子问题 f[i][j-2]
2.2、如果p[j-1]与s[i]匹配
2.2.1、最先想到的是一种情况:因为s[i]已经与p[j-1]匹配了,所以我们要看s[i]与p[j−2]的匹配,也就是转化为子问题:f[i][j-2]
2.2.2、其次还有一种情况:2.2.1考虑的是p[j]是p[j-1]重复的情况,本场景需要考虑的是s[i]与s[i-1]相同的情况,那就看s[i−1]与p[j] 的匹配,也就是转化为子问题:f[i-1][j]
2.2.3、综上,那么即可写出公式:
3、最终的状态转换方程如下:
4、状态初始化:
4.1、动态规划的边界条件为 f[0][0]=true,f[0][n](n>0)=false,也就是空字符串s与空字符串p相等,而空字符串s与任何的非空字符串p都不相等。
Python代码:
class Solution(object):def isMatch(self, s, p):""":type s: str:type p: str:rtype: bool"""sL = len(s)pL = len(p)f = [[False for j in range(pL + 1)] for i in range(sL + 1)]f[0][0] = Truefor i in range(0, sL + 1):for j in range(1, pL + 1):if p[j - 1] != '*':if match(s,i,p,j):f[i][j] = f[i -1][j - 1]else:if match(s,i, p ,j -1):f[i][j] = f[i - 1][j] or f[i][j - 2]else:f[i][j] = f[i][j - 2]return f[sL][pL]def match(s, i, p, j):if s[i - 1] == p[j - 1]:return Trueif p[j - 1] == '.':return Truereturn False
总结:
1、动态规划是逆向思考问题,从后往前进行归纳,也就是思考f(n+1)是如何由f(n)得到的问题
2、动态规划方程的初始化也需要思考清楚,相关的边界条件要想好