题目:给你一个字符串 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 的小写字母,以及字符 . 和 *。
- 保证每次出现字符 * 时,前面都匹配到有效的字符
以下是对上述正则表达式匹配算法的解题思路分析:
一、问题分析
给定一个字符串s
和一个正则表达式模式p
,要求判断s
是否能被p
匹配。正则表达式中的.
可以匹配任意单个字符,*
可以匹配零个或多个前面的那一个元素。
二、动态规划思路
-
定义状态:
- 使用一个二维布尔数组
dp[i][j]
表示s
的前i
个字符和p
的前j
个字符是否能够匹配。
- 使用一个二维布尔数组
-
初始化状态:
dp[0][0]
表示空字符串和空模式是匹配的,所以初始化为true
。- 对于模式
p
为空字符串时,只有当s
也为空字符串时才能匹配,所以只有dp[0][0]
为true
,其他dp[0][j]
(j > 0
)需要根据模式p
中的*
进行初始化。 - 如果
p
的第j
个字符是*
,那么dp[0][j] = dp[0][j - 2]
,因为*
可以匹配零个前面的字符,所以相当于忽略掉前一个字符和*
这个组合。
-
状态转移方程:
- 当
p
的第j
个字符是.
或者与s
的第i
个字符相同时,即p.charAt(j - 1) == '.' || p.charAt(j - 1) == s.charAt(i - 1)
,此时dp[i][j]
的值取决于s
的前i - 1
个字符和p
的前j - 1
个字符是否匹配,即dp[i][j] = dp[i - 1][j - 1]
。 - 当
p
的第j
个字符是*
时:- 首先考虑
*
匹配零个前面的字符的情况,此时dp[i][j] = dp[i][j - 2]
。 - 然后考虑
*
匹配一个或多个前面的字符的情况,只有当p
的第j - 1
个字符是.
或者与s
的第i
个字符相同时,即p.charAt(j - 2) == '.' || p.charAt(j - 2) == s.charAt(i - 1)
,并且s
的前i - 1
个字符和p
的前j
个字符匹配(因为*
可以匹配多个相同的字符),即dp[i - 1][j]
为true
时,dp[i][j]
才为true
。所以这种情况下dp[i][j] = dp[i][j] || dp[i - 1][j]
。
- 首先考虑
- 如果不满足以上任何一种情况,那么
dp[i][j] = false
。
- 当
-
最终结果:
dp[m][n]
表示s
的全部m
个字符和p
的全部n
个字符是否匹配,即为最终结果。
三、时间复杂度和空间复杂度分析
-
时间复杂度:
- 计算每个状态
dp[i][j]
的时间复杂度是 O ( 1 ) O(1) O(1),总共有 m ∗ n m * n m∗n个状态,所以时间复杂度是 O ( m n ) O(mn) O(mn),其中m
是字符串s
的长度,n
是模式p
的长度。
- 计算每个状态
-
空间复杂度:
- 使用了一个二维数组
dp
来保存状态,大小为(m + 1) * (n + 1)
,所以空间复杂度是 O ( m n ) O(mn) O(mn)。可以通过优化空间复杂度,只使用一维数组或者滚动数组来降低空间复杂度到 O ( n ) O(n) O(n)。
- 使用了一个二维数组