LeetCode:Regular Expression Matching
题目描述
给定输入字符串s
,和模式字符串p
:
-
s
只包含小写字母,assert( 0 <= len(s) <= 20)
。 -
p
只包含小写字母,特殊字符.
、*
:-
.
:匹配任意一个字符。 -
*
:匹配[0, ~]个前置字符。 assert( 0 <= len(p) <= 30)
-
- 输入保证,```*``前面必定有有效的字符。
解题思路
通用的字符串匹配算法可以采用回朔的方式,采用模式栈+匹配成功字符栈进行匹配:
- 当模式栈内容等于
p
,匹配成功栈内容等于s
时,匹配成功。 - 当
s
匹配成功,p
未完结,需要跳过字符*
的无效对。 - 当匹配字符是普通字符和
.
时,匹配栈和匹配栈按照匹配结果按序压栈即可。 - 当下一个匹配字符为
*
,会产生下列分流:-
*
和当前匹配字符入匹配栈。 - 待匹配字符和上一个匹配字符相等,待匹配字符入匹配成功字符栈,继续匹配当前字符。
-
最终可得到如下算法:
class Solution:
def isMatch(self, s, p):
return self._is_match(s, 0, p, 0)
def _is_match(self, s, s_index, p, p_index):
if s_index == len(s):
if p_index == (len(p) - 1) and p[p_index] == "*": # 最后一个是*,跳过
p_index = p_index + 1
while (p_index + 1) < len(p): # 跳过末尾的 "字符*"对
if p[p_index+1] != "*":
break
p_index = p_index + 2
if p_index == len(p):
return True
if s_index == len(s) or p_index == len(p):
return False
is_matched = (p[p_index] == "." or p[p_index] == s[s_index])
if is_matched: # 匹配一个字符
if self._is_match(s, s_index+1, p, p_index+1):
return True
if (p_index + 1) < len(p) and p[p_index+1] == "*":
if is_matched and self._is_match(s, s_index+1, p, p_index): # 再使用前一个字符
return True
if self._is_match(s, s_index, p, p_index+2): # 跳过前一个字符
return True
return False
回朔的方法的时间复杂度较高,除此解法外,本题还适用于动态规划。
动态规划问题需要满足:
- 问题具有最优子结构性质:如果问题的最优解包含的子问题的解也是最优的,我们就称该问题具有最优子结构。
- 无后效性:当前的若干个状态值一旦确定,则此后过程的演变就只和这若干个状态的值有关,和之前是采取哪种手段或经过哪条路径演变到当前的这个若干个状态,没有关系。
动态规划的常见解题步骤:
- 将问题分解成子问题。
- 确定状态。
- 确定初始状态。
- 确定状态转移方程。
下面以上述方法对题目进行解构:
假设:
dp = [[False for _ in range (len(s) + 1)] for _ in range(len(p) + 1)]
# y 为 当前s中待匹配字符的index + 1
# x 为 当前p中匹配字符的index + 1
- 本题的子问题在于
p
中当前匹配字符和s
中待匹配字符是否匹配。 - 状态为当前匹配字符和待匹配字符:
- 上一个字符的匹配情况。
- 上两个字符的匹配情况。
- 初始状态的匹配情况。
- 状态空间为所有字符的匹配情况。
- 通过下图分析初始状态:
dp[0][0] = True
-
True
向所有字符*
组合扩散。
- 通过下图确定转移方程:
- 字符为非
*
:dp[x][y]= (dp[x-1][y-1] and s[y-1] == p[x-1] or p[x-1] == ".")
- 当字符为
*
:dp[x][y] |= dp[x-2][y]
dp[x][y] |= (dp[x-1][y-1] and s[x-1] == p[y-1] or p[y-1] == "."))
- 字符为非
最终得到算法:
class Solution:
def isMatch(self, s, p):
dp = [[False for _ in range (len(s) + 1)] for _ in range(len(p) + 1)]
dp[0][0] = True
for i in range(1, len(p)+1):
if i >= 2:
dp[i][0] = dp[i-2][0] and p[i-1] == "*"
for i in range(1, len(p)+1):
for j in range(1, len(s)+1):
if p[i-1] == "*":
dp[i][j] = (dp[i][j-1] and p[i-2] in (s[j-1], ".")) or dp[i-2][j]
else:
dp[i][j] = dp[i-1][j-1] and p[i-1] in (s[j-1], ".")
return dp[len(p)][len(s)]