一、题目描述
给你一个输入字符串 (s
) 和一个字符模式 (p
) ,请你实现一个支持 '?'
和 '*'
匹配规则的通配符匹配:
'?'
可以匹配任何单个字符。'*'
可以匹配任意字符序列(包括空字符序列)。
判定匹配成功的充要条件是:字符模式必须能够 完全匹配 输入字符串(而不是部分匹配)。
示例 1:
输入:s = "aa", p = "a" 输出:false 解释:"a" 无法匹配 "aa" 整个字符串。
示例 2:
输入:s = "aa", p = "*" 输出:true 解释:'*' 可以匹配任意字符串。
示例 3:
输入:s = "cb", p = "?a" 输出:false 解释:'?' 可以匹配 'c', 但第二个 'a' 无法匹配 'b'。
提示:
0 <= s.length, p.length <= 2000
s
仅由小写英文字母组成p
仅由小写英文字母、'?'
或'*'
组成
二、解题思路
1. 基本情况:如果模式 p
已经完全遍历完毕,那么只有当字符串 s
也完全遍历完毕时,整个模式才算匹配成功。这可以通过检查 pi
(模式的当前位置)是否等于 p.length()
来确定。
2. 处理 '*' 字符:如果当前模式字符是 '*'
,我们需要考虑 '*'
可以匹配的情况:
为了处理这些情况,我们递归地调用 match
函数,分别尝试上述三种匹配方式,并返回 true
如果其中任何一种方式能够匹配成功。
'*'
可以匹配零个字符,即忽略当前的'*'
,继续检查后面的模式字符。'*'
可以匹配一个字符,包括字符串的当前字符。'*'
可以匹配多个字符,包括当前字符及后面的所有字符。
3. 处理其他字符:如果当前模式字符不是 '*'
,我们检查它是否与字符串的当前字符相等,或者模式字符是 '?'
( '?'
可以匹配任何单个字符)。如果当前字符匹配成功,我们递归地调用 match
函数,继续检查下一个字符。
4. 递归函数:递归函数 match
接受四个参数:字符串 s
,模式 p
,以及它们各自的当前位置 si
和 pi
。函数首先检查基本情况,然后根据当前模式字符是否为 '*'
或 '?'
来决定如何处理匹配。
三、具体代码
class Solution {
public boolean isMatch(String s, String p) {
return match(s, p, 0, 0);
}
private boolean match(String s, String p, int si, int pi) {
// 如果模式已经遍历完,字符串也必须遍历完才算匹配
if (pi == p.length()) return si == s.length();
// 如果当前模式字符是 '*',则有三种情况:
// 1. '*' 匹配空字符串,继续匹配下一个字符
// 2. '*' 匹配一个字符,包括字符串的当前字符
// 3. '*' 匹配多个字符,包括当前字符及后面的所有字符
boolean isStar = pi < p.length() && p.charAt(pi) == '*';
if (isStar) {
return (match(s, p, si, pi + 1) || // 情况 1
si < s.length() && (match(s, p, si + 1, pi) || // 情况 2
match(s, p, si + 1, pi + 1))); // 情况 3
}
// 如果当前模式字符是 '?' 或与字符串当前字符相等,则可以匹配一个字符
boolean isMatch = si < s.length() && (p.charAt(pi) == '?' || s.charAt(si) == p.charAt(pi));
return isMatch && match(s, p, si + 1, pi + 1);
}
}
四、时间复杂度和空间复杂度
这段代码的时间复杂度和空间复杂度取决于递归的深度和递归调用的次数。
1. 时间复杂度
- 在最坏的情况下,对于每个字符在模式 `p` 中的 `'*'`,我们可能需要递归地检查字符串 `s` 中的每个字符序列,以确定是否匹配。
- 这意味着对于长度为 `n` 的字符串 `s` 和长度为 `m` 的模式 `p`,我们可能需要进行 `O(n * m)` 的递归调用。
- 因此,时间复杂度在最坏的情况下是 `O(n * m)`。
2. 空间复杂度
- 空间复杂度主要由递归调用栈的深度决定。
- 在最坏的情况下,我们可能需要存储所有递归调用的状态,每个调用使用常量空间。
- 因此,空间复杂度也是 `O(n * m)`,其中 `n` 是字符串 `s` 的长度,`m` 是模式 `p` 的长度。
- 然而,值得注意的是,实际的空间消耗可能会小于 `O(n * m)`,因为递归调用可能会共享相同的状态(例如,相同的 `si` 和 `pi` 值),并且由于尾递归优化,一些递归调用可能会被重用。但是,没有尾递归优化的保证,我们仍然按照最坏情况来估计空间复杂度。
五、总结知识点
1. 递归:代码使用了递归来解决问题。递归是一种通过将问题分解为更小的、相似的子问题来解决问题的编程技术。在这段代码中,递归函数 match
被用来检查字符串 s
是否与模式 p
匹配。
2. 字符串匹配:代码实现了一个字符串匹配算法,该算法考虑了两种特殊字符 '?'
和 '*'
。'?'
可以匹配任何单个字符,而 '*'
可以匹配零个或多个字符。
3. 处理通配符 '*'
:代码中特别处理了 '*'
字符,它有三种可能的匹配情况:
这些情况通过递归调用 match
函数来处理,确保了 '*'
字符的灵活性。
'*'
匹配空字符串。'*'
匹配一个字符。'*'
匹配多个字符。
4. 边界条件:在递归函数中,首先检查了模式 p
是否已经完全遍历完毕。如果 p
已经遍历完毕,则只有当字符串 s
也完全遍历完毕时,才算匹配成功。
5. 字符比较:代码中还包含了对字符串中当前字符的比较逻辑。如果模式中的当前字符是 '?'
或者与字符串中的当前字符相等,那么这两个字符被认为是匹配的。
6. 尾递归:虽然代码中没有显式地优化尾递归,但是在处理 '*'
字符时,递归调用通常是在当前函数调用的末尾进行的,这在某些编程语言中可能会被编译器优化为迭代,从而减少栈空间的使用。
7. 错误处理:代码没有显式的错误处理逻辑,它假设输入的字符串 s
和模式 p
都是有效的,并且只包含小写字母、'?'
和 '*'
字符。
以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。