leetcode题解之44. 通配符匹配
给定一个字符串 (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
方法一:带记忆的递归
这里的第一个想法是递归,是一个较为简单的方法,但是如果输入的字符串过长会导致递归深度很大,因此比较耗时。
- 如果字符串相等
p == s,返回True。 - 如果
p == '*',返回True。 - 如果
p为空或s为空,返回False。 - 若当前字符匹配,即
p[0] == s[0]或p[0] == '?',然后比较下一个字符,返回isMatch(s[1:], p[1:])。 - 如果当前的字符模式是一个星号
p[0] == '*',则有两种情况。- 星号没有匹配字符,因此答案是
isMatch(s, p[1:])。 - 星号匹配一个字符或更多字符,因此答案是
isMatch(s[1:], p)。 - 若
p[0] != s[0],返回False。
- 星号没有匹配字符,因此答案是
这个算法由于时间限制,他没有通过所有的测试用例,因此必须进行优化,以下是可以做到的:
- 记忆。这是优化递归的标准方法。我们使用
(s, p)作为键,使用匹配与不匹配作为布尔值。创建一个记忆的哈希映射。将所有已经检查的(s, p)保留在哈希映射中。这样,如果有任何重复的检查,只需查看哈希表,而不需再次进行计算。 - 清理输入数据,不管
a****bc**cc中有多少个星号,它们都可以简化为a*bc*cc。这样的清理有助于减少递归深度。
算法:
- 清理输入数据用一个星号代替多个星号:
p = remove_duplicate_stars(p)。 - 初始化记忆哈希表
dp。 - 返回
helper函数,用清理后的输入作为参数。 helper(s,p):- 如果
(s,p)已经计算过存储在dp中,则返回dp中的值。 - 如果字符串相等
p == s或p == '*',在dp添加dp[(s, p)] = True。 - 反之如果
p或s为空,则dp[(s, p)] = False。 - 反之如果当前字符匹配,即
p[0] == s[0]或p[0] == '?',则继续检查下一个字符并dp[(s, p)] = helper(s[1:], p[1:])。 - 反之如果当前字符模式是一个星号
p[0] == '*',则有两种情况,要么不匹配字符要么匹配一个或多个,符,则dp[(s, p)] = helper(s, p[1:]) or helper(s, p[1:])。 - 反之如果
p[0] != s[0],则dp[(s, p)] = False。 - 返回
dp[(s, p)]。
- 如果
class Solution:
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)
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">helper</span><span class="hljs-params">(self, s, p)</span>:</span>
dp = self.dp
<span class="hljs-keyword">if</span> (s, p) <span class="hljs-keyword">in</span> dp:
<span class="hljs-keyword">return</span> dp[(s, p)]
<span class="hljs-keyword">if</span> p == s <span class="hljs-keyword">or</span> p == <span class="hljs-string">'*'</span>:
dp[(s, p)] = <span class="hljs-literal">True</span>
<span class="hljs-keyword">elif</span> p == <span class="hljs-string">''</span> <span class="hljs-keyword">or</span> s == <span class="hljs-string">''</span>:
dp[(s, p)] = <span class="hljs-literal">False</span>
<span class="hljs-keyword">elif</span> p[<span class="hljs-number">0</span>] == s[<span class="hljs-number">0</span>] <span class="hljs-keyword">or</span> p[<span class="hljs-number">0</span>] == <span class="hljs-string">'?'</span>:
dp[(s, p)] = self.helper(s[<span class="hljs-number">1</span>:], p[<span class="hljs-number">1</span>:])
<span class="hljs-keyword">elif</span> p[<span class="hljs-number">0</span>] == <span class="hljs-string">'*'</span>:
dp[(s, p)] = self.helper(s, p[<span class="hljs-number">1</span>:]) <span class="hljs-keyword">or</span> self.helper(s[<span class="hljs-number">1</span>:], p)
<span class="hljs-keyword">else</span>:
dp[(s, p)] = <span class="hljs-literal">False</span>
<span class="hljs-keyword">return</span> dp[(s, p)]
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">isMatch</span><span class="hljs-params">(self, s, p)</span>:</span>
p = self.remove_duplicate_stars(p)
<span class="hljs-comment"># memorization hashmap to be used during the recursion</span>
self.dp = {}
<span class="hljs-keyword">return</span> self.helper(s, p)
复杂度分析
- 时间复杂度:最好的情况下 ,最坏的情况下是 。其中 和 指的是输入字符串和字符模式的长度。 最好的情况很明显,让我们估算最坏的情况。最耗时的递归是字符模式上的星号形成树的情况,将执行两个分支
helper(s, p[1:])和helper(s[1:], p)。数据清理后字符模式中的最大星树为 ,因此时间复杂度为 。 - 空间复杂度:,用来存储记忆哈希表和递归调用堆栈。
方法二:动态规划
上面的递归方法体现了当递归深度大的时候有多耗时,所以我们尝试一些更迭代的方法。
第一种方法中的记忆给出了尝试动态规划的想法。这个问题和编辑距离非常相似,所以我们在这里使用完全相同的方法。
我们的想法是将问题简化为简单的问题,例如,有一个字符串 adcebdk 和字符模式 *a*b?k,计算是否匹配 D = True/False。我们将输入字符串和字符模式的长度 p_len,s_len 和是否匹配 D[p_len][s_len] 联系起来。
让我们进一步介绍 D[p_idx][s_idx],D[p_idx][s_idx] 代表的是字符模式中的第 p_idx 字符和输入字符串的第 s_idx 字符是否匹配。
如果字符相同或字符模式的字符为 ?,则
如果字符模式的字符为星号且 D[p_idx - 1][s_idx - 1] = True,则:
- 星号匹配完成。
- 星号继续匹配更多的字符。
所以,每一步的计算是基于之前完成的计算完成的。
算法:
- 初始化匹配表为
False除了D[0][0] = True。 - 使用规则 1 和规则 2 计算表格,最后返回
D[p_len][s_len]作为答案。
class Solution:
def isMatch(self, s, p):
s_len = len(s)
p_len = len(p)
<span class="hljs-comment"># base cases</span>
<span class="hljs-keyword">if</span> p == s <span class="hljs-keyword">or</span> p == <span class="hljs-string">'*'</span>:
<span class="hljs-keyword">return</span> <span class="hljs-literal">True</span>
<span class="hljs-keyword">if</span> p == <span class="hljs-string">''</span> <span class="hljs-keyword">or</span> s == <span class="hljs-string">''</span>:
<span class="hljs-keyword">return</span> <span class="hljs-literal">False</span>
<span class="hljs-comment"># init all matrix except [0][0] element as False</span>
d = [ [<span class="hljs-literal">False</span>] * (s_len + <span class="hljs-number">1</span>) <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> range(p_len + <span class="hljs-number">1</span>)]
d[<span class="hljs-number">0</span>][<span class="hljs-number">0</span>] = <span class="hljs-literal">True</span>
<span class="hljs-comment"># DP compute </span>
<span class="hljs-keyword">for</span> p_idx <span class="hljs-keyword">in</span> range(<span class="hljs-number">1</span>, p_len + <span class="hljs-number">1</span>):
<span class="hljs-comment"># the current character in the pattern is '*'</span>
<span class="hljs-keyword">if</span> p[p_idx - <span class="hljs-number">1</span>] == <span class="hljs-string">'*'</span>:
s_idx = <span class="hljs-number">1</span>
<span class="hljs-comment"># d[p_idx - 1][s_idx - 1] is a string-pattern match </span>
<span class="hljs-comment"># on the previous step, i.e. one character before.</span>
<span class="hljs-comment"># Find the first idx in string with the previous math.</span>
<span class="hljs-keyword">while</span> <span class="hljs-keyword">not</span> d[p_idx - <span class="hljs-number">1</span>][s_idx - <span class="hljs-number">1</span>] <span class="hljs-keyword">and</span> s_idx < s_len + <span class="hljs-number">1</span>:
s_idx += <span class="hljs-number">1</span>
<span class="hljs-comment"># If (string) matches (pattern), </span>
<span class="hljs-comment"># when (string) matches (pattern)* as well</span>
d[p_idx][s_idx - <span class="hljs-number">1</span>] = d[p_idx - <span class="hljs-number">1</span>][s_idx - <span class="hljs-number">1</span>]
<span class="hljs-comment"># If (string) matches (pattern), </span>
<span class="hljs-comment"># when (string)(whatever_characters) matches (pattern)* as well</span>
<span class="hljs-keyword">while</span> s_idx < s_len + <span class="hljs-number">1</span>:
d[p_idx][s_idx] = <span class="hljs-literal">True</span>
s_idx += <span class="hljs-number">1</span>
<span class="hljs-comment"># the current character in the pattern is '?'</span>
<span class="hljs-keyword">elif</span> p[p_idx - <span class="hljs-number">1</span>] == <span class="hljs-string">'?'</span>:
<span class="hljs-keyword">for</span> s_idx <span class="hljs-keyword">in</span> range(<span class="hljs-number">1</span>, s_len + <span class="hljs-number">1</span>):
d[p_idx][s_idx] = d[p_idx - <span class="hljs-number">1</span>][s_idx - <span class="hljs-number">1</span>]
<span class="hljs-comment"># the current character in the pattern is not '*' or '?'</span>
<span class="hljs-keyword">else</span>:
<span class="hljs-keyword">for</span> s_idx <span class="hljs-keyword">in</span> range(<span class="hljs-number">1</span>, s_len + <span class="hljs-number">1</span>):
<span class="hljs-comment"># Match is possible if there is a previous match</span>
<span class="hljs-comment"># and current characters are the same</span>
d[p_idx][s_idx] = \
d[p_idx - <span class="hljs-number">1</span>][s_idx - <span class="hljs-number">1</span>] <span class="hljs-keyword">and</span> p[p_idx - <span class="hljs-number">1</span>] == s[s_idx - <span class="hljs-number">1</span>]
<span class="hljs-keyword">return</span> d[p_len][s_len]
class Solution {
public boolean isMatch(String s, String p) {
int sLen = s.length(), pLen = p.length();
<span class="hljs-comment">// base cases</span>
<span class="hljs-keyword">if</span> (p.equals(s) || p.equals(<span class="hljs-string">"*"</span>)) <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;
<span class="hljs-keyword">if</span> (p.isEmpty() || s.isEmpty()) <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;
<span class="hljs-comment">// init all matrix except [0][0] element as False</span>
<span class="hljs-keyword">boolean</span>[][] d = <span class="hljs-keyword">new</span> <span class="hljs-keyword">boolean</span>[pLen + <span class="hljs-number">1</span>][sLen + <span class="hljs-number">1</span>];
d[<span class="hljs-number">0</span>][<span class="hljs-number">0</span>] = <span class="hljs-keyword">true</span>;
<span class="hljs-comment">// DP compute</span>
<span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> pIdx = <span class="hljs-number">1</span>; pIdx < pLen + <span class="hljs-number">1</span>; pIdx++) {
<span class="hljs-comment">// the current character in the pattern is '*'</span>
<span class="hljs-keyword">if</span> (p.charAt(pIdx - <span class="hljs-number">1</span>) == <span class="hljs-string">'*'</span>) {
<span class="hljs-keyword">int</span> sIdx = <span class="hljs-number">1</span>;
<span class="hljs-comment">// d[p_idx - 1][s_idx - 1] is a string-pattern match</span>
<span class="hljs-comment">// on the previous step, i.e. one character before.</span>
<span class="hljs-comment">// Find the first idx in string with the previous math.</span>
<span class="hljs-keyword">while</span> ((!d[pIdx - <span class="hljs-number">1</span>][sIdx - <span class="hljs-number">1</span>]) && (sIdx < sLen + <span class="hljs-number">1</span>)) sIdx++;
<span class="hljs-comment">// If (string) matches (pattern),</span>
<span class="hljs-comment">// when (string) matches (pattern)* as well</span>
d[pIdx][sIdx - <span class="hljs-number">1</span>] = d[pIdx - <span class="hljs-number">1</span>][sIdx - <span class="hljs-number">1</span>];
<span class="hljs-comment">// If (string) matches (pattern),</span>
<span class="hljs-comment">// when (string)(whatever_characters) matches (pattern)* as well</span>
<span class="hljs-keyword">while</span> (sIdx < sLen + <span class="hljs-number">1</span>) d[pIdx][sIdx++] = <span class="hljs-keyword">true</span>;
}
<span class="hljs-comment">// the current character in the pattern is '?'</span>
<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (p.charAt(pIdx - <span class="hljs-number">1</span>) == <span class="hljs-string">'?'</span>) {
<span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> sIdx = <span class="hljs-number">1</span>; sIdx < sLen + <span class="hljs-number">1</span>; sIdx++)
d[pIdx][sIdx] = d[pIdx - <span class="hljs-number">1</span>][sIdx - <span class="hljs-number">1</span>];
}
<span class="hljs-comment">// the current character in the pattern is not '*' or '?'</span>
<span class="hljs-keyword">else</span> {
<span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> sIdx = <span class="hljs-number">1</span>; sIdx < sLen + <span class="hljs-number">1</span>; sIdx++) {
<span class="hljs-comment">// Match is possible if there is a previous match</span>
<span class="hljs-comment">// and current characters are the same</span>
d[pIdx][sIdx] = d[pIdx - <span class="hljs-number">1</span>][sIdx - <span class="hljs-number">1</span>] &&
(p.charAt(pIdx - <span class="hljs-number">1</span>) == s.charAt(sIdx - <span class="hljs-number">1</span>));
}
}
}
<span class="hljs-keyword">return</span> d[pLen][sLen];
}
}
复杂度分析
- 时间复杂度:,其中 和 指的是字符模式和输入字符串的长度。
- 空间复杂度:,用来存储匹配表格。
方法三:回溯
复杂度 比 好的多,但是仍然有改进的余地。不需要计算整个表格,也就是检查每个星号的所有可能性:
- 匹配 0 个字符。
- 匹配 1 个字符。
- 匹配 2 个字符。
...
- 匹配所有剩余的字符。
让我们从匹配 0 个字符开始,如果这个假设导致不匹配,则回溯:回到前一个星号,假设它匹配一个字符,然后继续。若又是不匹配的情况?再次回溯:回到上一个星号,假设匹配两个字符,等等。
算法:
我们使用两个指针:s_idx 遍历输入字符串,p_idx 遍历字符模式。当 s_idx < s_len:
- 如果字符模式仍有字符
p_idx < p_len且指针下的字符匹配p[p_idx] == s[s_idx]或p[p_idx] == '?',则两个指针向前移动。 - 反之如果字符模式仍有字符
p_idx < p_len且p[p_idx] == '*',则首先检查匹配 0 字符的情况,即只增加模式指针p_idx++。记下可能回溯的位置star_idx和当前字符串的位置s_tmp_idx。 - 反之如果出现不匹配的情况:
- 如果字符模式中没有星号,则返回
False。 - 如果有星号,则回溯:设置
p_idx = star_idx + 1和s_idx = s_tmp_idx + 1,假设这次的星匹配多个字符。则可能的回溯为s_tmp_idx = s_idx。
- 如果字符模式中没有星号,则返回
- 如果字符模式的所有剩余字符都是星号,则返回
True。
class Solution:
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
<span class="hljs-keyword">while</span> s_idx < s_len:
<span class="hljs-comment"># If the pattern caracter = string character</span>
<span class="hljs-comment"># or pattern character = '?'</span>
<span class="hljs-keyword">if</span> p_idx < p_len <span class="hljs-keyword">and</span> p[p_idx] <span class="hljs-keyword">in</span> [<span class="hljs-string">'?'</span>, s[s_idx]]:
s_idx += <span class="hljs-number">1</span>
p_idx += <span class="hljs-number">1</span>
<span class="hljs-comment"># If pattern character = '*'</span>
<span class="hljs-keyword">elif</span> p_idx < p_len <span class="hljs-keyword">and</span> p[p_idx] == <span class="hljs-string">'*'</span>:
<span class="hljs-comment"># Check the situation</span>
<span class="hljs-comment"># when '*' matches no characters</span>
star_idx = p_idx
s_tmp_idx = s_idx
p_idx += <span class="hljs-number">1</span>
<span class="hljs-comment"># If pattern character != string character</span>
<span class="hljs-comment"># or pattern is used up</span>
<span class="hljs-comment"># and there was no '*' character in pattern </span>
<span class="hljs-keyword">elif</span> star_idx == <span class="hljs-number">-1</span>:
<span class="hljs-keyword">return</span> <span class="hljs-literal">False</span>
<span class="hljs-comment"># If pattern character != string character</span>
<span class="hljs-comment"># or pattern is used up</span>
<span class="hljs-comment"># and there was '*' character in pattern before</span>
<span class="hljs-keyword">else</span>:
<span class="hljs-comment"># Backtrack: check the situation</span>
<span class="hljs-comment"># when '*' matches one more character</span>
p_idx = star_idx + <span class="hljs-number">1</span>
s_idx = s_tmp_idx + <span class="hljs-number">1</span>
s_tmp_idx = s_idx
<span class="hljs-comment"># The remaining characters in the pattern should all be '*' characters</span>
<span class="hljs-keyword">return</span> all(x == <span class="hljs-string">'*'</span> <span class="hljs-keyword">for</span> x <span class="hljs-keyword">in</span> p[p_idx:])
class Solution {
public boolean isMatch(String s, String p) {
int sLen = s.length(), pLen = p.length();
int sIdx = 0, pIdx = 0;
int starIdx = -1, sTmpIdx = -1;
<span class="hljs-keyword">while</span> (sIdx < sLen) {
<span class="hljs-comment">// If the pattern caracter = string character</span>
<span class="hljs-comment">// or pattern character = '?'</span>
<span class="hljs-keyword">if</span> (pIdx < pLen && (p.charAt(pIdx) == <span class="hljs-string">'?'</span> || p.charAt(pIdx) == s.charAt(sIdx))){
++sIdx;
++pIdx;
}
<span class="hljs-comment">// If pattern character = '*'</span>
<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (pIdx < pLen && p.charAt(pIdx) == <span class="hljs-string">'*'</span>) {
<span class="hljs-comment">// Check the situation</span>
<span class="hljs-comment">// when '*' matches no characters</span>
starIdx = pIdx;
sTmpIdx = sIdx;
++pIdx;
}
<span class="hljs-comment">// If pattern character != string character</span>
<span class="hljs-comment">// or pattern is used up</span>
<span class="hljs-comment">// and there was no '*' character in pattern </span>
<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (starIdx == -<span class="hljs-number">1</span>) {
<span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;
}
<span class="hljs-comment">// If pattern character != string character</span>
<span class="hljs-comment">// or pattern is used up</span>
<span class="hljs-comment">// and there was '*' character in pattern before</span>
<span class="hljs-keyword">else</span> {
<span class="hljs-comment">// Backtrack: check the situation</span>
<span class="hljs-comment">// when '*' matches one more character</span>
pIdx = starIdx + <span class="hljs-number">1</span>;
sIdx = sTmpIdx + <span class="hljs-number">1</span>;
sTmpIdx = sIdx;
}
}
<span class="hljs-comment">// The remaining characters in the pattern should all be '*' characters</span>
<span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i = pIdx; i < pLen; i++)
<span class="hljs-keyword">if</span> (p.charAt(i) != <span class="hljs-string">'*'</span>) <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;
<span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;
}
}
复杂度分析
- 时间复杂度:最好的情况下是 ,平均情况下是 ,其中 和 指的是字符模式和输入字符串的长度。详细证明可点击:证明过程。
- 空间复杂度:。

浙公网安备 33010602011771号