题目
给定一个字符串 (s)
和一个字符模式 (p)
,实现一个支持 '?'
和 '*'
的通配符匹配。
'?'
可以匹配任何单个字符。
'*'
可以匹配任意字符串(包括空字符串)。
两个字符串完全匹配才算匹配成功。
说明:
s
可能为空,且只包含从a-z
的小写字母。p
可能为空,且只包含从a-z
的小写字母,以及字符?
和*
。
示例1:
输入:
s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。
示例2:
输入:
s = "aa"
p = "*"
输出: true
解释: '*' 可以匹配任意字符串。
示例3:
输入:
s = "cb"
p = "?a"
输出: false
解释: '?' 可以匹配 'c', 但第二个 'a' 无法匹配 'b'。
示例4:
输入:
s = "adceb"
p = "*a*b"
输出: true
解释: 第一个 '*' 可以匹配空字符串, 第二个 '*' 可以匹配字符串 "dce".
示例5:
输入:
s = "acdcb"
p = "a*c?b"
输入: false
优化递归
算法实现
def remove_duplicate_stars(self, p):
if p == '':
return p
p1 = [p[0], ]
for x in p[1:]:
if p1[-1] != '*' or p1[-1] == '*' and x != '*':
p1.append(x)
return ''.join(p1)
def helper(self, s, p):
dp = self.dp
if (s, p) in dp:
return dp[(s, p)]
if p == s or p == '*':
dp[(s, p)] = True
elif p == '' or s == '':
dp[(s, p)] = False
elif p[0] == s[0] or p[0] == '?':
dp[(s, p)] = self.helper(s[1:], p[1:])
elif p[0] == '*':
dp[(s, p)] = self.helper(s, p[1:]) or self.helper(s[1:], p)
else:
dp[(s, p)] = False
return dp[(s, p)]
def isMatch(self, s, p):
p = self.remove_duplicate_stars(p)
# memorization hashmap to be used during the recursion
self.dp = {}
return self.helper(s, p)
执行结果
执行结果 : 通过
执行用时 : 1244 ms, 在所有 Python3 提交中击败了18.32%的用户
内存消耗 : 664.2 MB, 在所有 Python3 提交中击败了5.51%的用户
复杂度分析
- 时间复杂度:最好的情况下 O ( min ( S , P ) ) \mathcal{O}(\min(S, P)) O(min(S,P)),最坏的情况下是 O ( 2 min ( S , P / 2 ) ) \mathcal{O}(2^{\min(S, P/2)}) O(2min(S,P/2))。其中 SS 和 PP 指的是输入字符串和字符模式的长度。 最好的情况很明显,让我们估算最坏的情况。最耗时的递归是字符模式上的星号形成树的情况,将执行两个分支 helper(s, p[1:]) 和 helper(s[1:], p)。数据清理后字符模式中的最大星树为 P / 2 P/2 P/2,因此时间复杂度为 O ( 2 min ( S , P / 2 ) ) \mathcal{O}(2^{\min(S, P/2)}) O(2min(S,P/2))。
- 空间复杂度: O ( 2 min ( S , P / 2 ) ) \mathcal{O}(2^{\min(S, P/2)}) O(2min(S,P/2)),用来存储记忆哈希表和递归调用堆栈。
动态规划
算法实现
def isMatch(self, s, p):
s_len = len(s)
p_len = len(p)
# base cases
if p == s or p == '*':
return True
if p == '' or s == '':
return False
# init all matrix except [0][0] element as False
d = [[False] * (s_len + 1) for _ in range(p_len + 1)]
d[0][0] = True
# DP compute
for p_idx in range(1, p_len + 1):
# the current character in the pattern is '*'
if p[p_idx - 1] == '*':
s_idx = 1
# d[p_idx - 1][s_idx - 1] is a string-pattern match
# on the previous step, i.e. one character before.
# Find the first idx in string with the previous math.
while not d[p_idx - 1][s_idx - 1] and s_idx < s_len + 1:
s_idx += 1
# If (string) matches (pattern),
# when (string) matches (pattern)* as well
d[p_idx][s_idx - 1] = d[p_idx - 1][s_idx - 1]
# If (string) matches (pattern),
# when (string)(whatever_characters) matches (pattern)* as well
while s_idx < s_len + 1:
d[p_idx][s_idx] = True
s_idx += 1
# the current character in the pattern is '?'
elif p[p_idx - 1] == '?':
for s_idx in range(1, s_len + 1):
d[p_idx][s_idx] = d[p_idx - 1][s_idx - 1]
# the current character in the pattern is not '*' or '?'
else:
for s_idx in range(1, s_len + 1):
# Match is possible if there is a previous match
# and current characters are the same
d[p_idx][s_idx] = \
d[p_idx - 1][s_idx - 1] and p[p_idx - 1] == s[s_idx - 1]
return d[p_len][s_len]
执行结果
复杂度分析
回溯
算法实现
def isMatch(self, s, p):
"""
:type s: str
:type p: str
:rtype: bool
"""
s_len, p_len = len(s), len(p)
s_idx = p_idx = 0
star_idx = s_tmp_idx = -1
while s_idx < s_len:
# If the pattern caracter = string character
# or pattern character = '?'
if p_idx < p_len and p[p_idx] in ['?', s[s_idx]]:
s_idx += 1
p_idx += 1
# If pattern character = '*'
elif p_idx < p_len and p[p_idx] == '*':
# Check the situation
# when '*' matches no characters
star_idx = p_idx
s_tmp_idx = s_idx
p_idx += 1
# If pattern character != string character
# or pattern is used up
# and there was no '*' character in pattern
elif star_idx == -1:
return False
# If pattern character != string character
# or pattern is used up
# and there was '*' character in pattern before
else:
# Backtrack: check the situation
# when '*' matches one more character
p_idx = star_idx + 1
s_idx = s_tmp_idx + 1
s_tmp_idx = s_idx
# The remaining characters in the pattern should all be '*' characters
return all(x == '*' for x in p[p_idx:])
执行结果
复杂度分析
- 时间复杂度:最好的情况下是 O ( min ( S , P ) ) ) \mathcal{O}(\min(S, P))) O(min(S,P))),平均情况下是 O ( S log P ) \mathcal{O}(S \log P) O(SlogP),其中 S S S 和 P P P 指的是字符模式和输入字符串的长度。
- 空间复杂度: O ( 1 ) \mathcal{O}(1) O(1)。
小结
只想说一句,好难啊啊啊啊~