动规训练四:Leetcode 10. 正则表达式
题目描述
给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。
‘.’ 匹配任意单个字符
‘*’ 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。
示例 1:
示例 2:
示例 3:
示例 4:
示例 5:
提示:
0 <= s.length <= 20
0 <= p.length <= 30
s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母,以及字符 . 和 *。
保证每次出现字符 * 时,前面都匹配到有效的字符
题解
这道题有一定难度,题解写了好几个小时,感觉不大好讲,不过我们 还是按照我们说的动态规划三部曲来,没看过的看我之前写的文章:告别动态规划,谈谈我的一些经验
一、定义数组的含义
字符串问题,90% 可以用动态规划来解决,对于这种有两个字符串的,基本都是二维数组来存放,我们定义 dp[i] [j] 的含义是当字符串 s 的长度为 i,模式串 p 的长度为 j 时,两者是否匹配,那么 dp[len_s] [len_p] 就是我们要的答案了。
注:len_s 表示字符串 s 的长度,len_p 表示 p 的长度。
二、找出数组元素之间的关系式
在比较字符的过程中,由于 p[j] 有三种类型的字符:.
,*
和普通字符,分以下情况讨论
1、当 p[j] 为 “.” 或者普通字符:这种情况比较简单,直接与 s[i] 进行匹配:
1) 如果匹配成功,那么就继续往下匹配,则有 dp[i] [j] = dp[i-1] [j-1]
2)如果匹配不成功,则有 dp[i] [j] = false。
2、如果 p[j] 为 *
时,稍微复杂一些,这里我们假设 p[j] 前面的一个字符是 b,即
分如下情况讨论
(1)因为 *
可以代表 0 个或多个前面的字符,所以我们必须要考虑他前面的元素 p[j-1]。因为 *
是跟着他前一个字符走,前一个能匹配上 s[i],*
才能有用,前一个都不能匹配上 s[i],*
也无能为力,只能让前一个字符消失,也就是匹配 0 次前一个字符,这里相当于把 p[j] 和 [j-1] 两个字符b*
都被删除了,那么有 dp[i] [j] = dp[i] [j – 2]。
也就是说,当 p[j-1] != s[i] 时,有 dp[i] [j] = dp[i] [j – 2]。
(2) 另一种就是当 p[j-1] = = s[i] 时,那么还会有如下三种情况:
1). 当 * 匹配 0 个字符时:其实和上面情况类似,相当于 p[j] 和 [j-1] 两个字符b*
都被删除了,那么有 dp[i] [j] = dp[i] [j – 2]。,
2). 当 * 匹配 1 个时:如果只匹配了一个,即 p[j] 和 [j-1] 两个字符b*
变成了一个字符 b
,这是相当于把 p[i] = *
这个字符删除了,那么有 dp[i] [j] = dp[i] [j-1]。
3). 当 * 匹配多个时:匹配多个时,相当于把 b*
变成了 bb*
、bbb*
等等,由于 b
是和 s[i] 匹配的,那我们可以拿 b
和 s[i] 相互抵消掉,然后 b*
还是保持原来的模样,和 s 后面的字符继续匹配,相当于把 s[i] 这个字符删除了,原理类似,这时有 dp[i] [j] = dp[i-1] [j]。
所以当 p[j-1] = = s[i] 时,有如下情况:
即
三、找出数组初始值
初始值还是比较容易找的,当两个字符串都是空字符串时,有 dp[0] [0] = true。
当 s 是空字符,p 不是空字符串时,我们需要判断 p[j] 是否为 *,如果为 *,则 dp[0] [j] = dp[0] [j – 2]。
当 s 不是空字符,p 是空字符串时,则有 dp[i][0] = false。
看代码吧,代码有详细解释