LeetCode 第44题:通配符匹配
LeetCode 第44题:通配符匹配
题目描述
给定一个字符串 (s
) 和一个字符模式 (p
) ,实现一个支持 '?'
和 '*'
的通配符匹配。
'?'
可以匹配任何单个字符。'*'
可以匹配任意字符序列(包括空序列)。
匹配应该覆盖整个字符串 (s
) ,而不是部分字符串。
难度
困难
题目链接
示例
示例 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
仅由小写英文字母,'?'
或'*'
组成
解题思路
动态规划解法
这道题可以使用动态规划来解决,我们定义 dp[i][j] 表示字符串 s 的前 i 个字符和模式 p 的前 j 个字符是否匹配。
关键点:
- 当 p[j-1] 是字母时,需要与 s[i-1] 完全匹配
- 当 p[j-1] 是 '?' 时,可以匹配任意单个字符
- 当 p[j-1] 是 '*' 时,可以匹配零个或多个字符
- 需要考虑空字符串的情况
具体步骤:
- 创建二维 dp 数组,初始化边界条件
- 遍历字符串和模式,根据不同字符更新 dp 数组
- 处理 '*' 的特殊情况
- 返回最终结果 dp[s.Length][p.Length]
图解思路
算法步骤分析表
步骤 | 操作 | dp状态 | 说明 |
---|---|---|---|
初始状态 | 初始化 | dp[0][0]=true | 空字符串匹配 |
第一步 | 处理 * | dp[0][j]=dp[0][j-1] | 处理开头的 * |
第二步 | 字符匹配 | dp[i][j]=dp[i-1][j-1] | 字符相等或 ? |
第三步 | 星号匹配 | dp[i][j]=dp[i][j-1]∣∣dp[i-1][j] | 处理 * |
状态/情况分析表
情况 | 输入 | 输出 | 说明 |
---|---|---|---|
完全匹配 | "aa","aa" | true | 精确匹配 |
问号匹配 | "aa","?a" | true | ? 匹配单字符 |
星号匹配 | "aa","*" | true | * 匹配全部 |
代码实现
C# 实现
public class Solution {
public bool IsMatch(string s, string p) {
int m = s.Length, n = p.Length;
bool[,] dp = new bool[m + 1, n + 1];
// 初始化空字符串匹配
dp[0, 0] = true;
// 处理开头的星号
for (int j = 1; j <= n; j++) {
if (p[j - 1] == '*') {
dp[0, j] = dp[0, j - 1];
}
}
// 填充dp数组
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (p[j - 1] == '*') {
dp[i, j] = dp[i - 1, j] || dp[i, j - 1];
} else if (p[j - 1] == '?' || s[i - 1] == p[j - 1]) {
dp[i, j] = dp[i - 1, j - 1];
}
}
}
return dp[m, n];
}
}
执行结果
- 执行用时:76 ms
- 内存消耗:39.8 MB
代码亮点
- 🎯 使用动态规划解决复杂的匹配问题
- 💡 巧妙处理 '*' 的匹配逻辑
- 🔍 清晰的状态转移方程
- 🎨 代码结构简洁,易于理解
常见错误分析
- 🚫 没有正确处理空字符串的情况
- 🚫 '*' 匹配逻辑处理不当
- 🚫 边界条件判断错误
- 🚫 状态转移方程写错
解法对比
解法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
---|---|---|---|---|
递归回溯 | O(2^n) | O(n) | 直观易懂 | 效率低 |
动态规划 | O(mn) | O(mn) | 效率高 | 空间消耗大 |
贪心算法 | O(mn) | O(1) | 空间优化 | 实现复杂 |
相关题目
- 正则表达式匹配 - 困难
- 编辑距离 - 困难
- 字符串的最小删除次数 - 中等