递归超时因重复计算子问题,最坏O(2^min(m,n));加memo用(i,j)状态缓存,DP转移中'*'需同时考虑匹配空(dpi)和非空(dpi-1)两种情况。

为什么 isMatch 递归写法在 "*" 多时会超时
通配符匹配中,"*" 可匹配任意长度字符串(包括空),递归天然会尝试所有分割点:比如模式 "***a" 配 "aaa",同一子问题被重复计算数十次。没有记忆化时,最坏时间复杂度是指数级 O(2^min(m,n)),实际跑 s = "a" * 100, p = "*" * 50 + "a" * 50 就卡死。
实操建议:
立即学习“C++免费学习笔记(深入)”;
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 加
memo表用pair<int int></int>或二维vector<vector>></vector>记录(i, j)状态,值为0(未算)、1(true)、-1(false) - 递归入口检查边界:若
j == p.size(),仅当i == s.size()才返回true;若i == s.size(),需跳过末尾所有"*"后再判断 - 别用
substr做参数传递——每次调用都拷贝子串,改用索引 + 原始引用
动态规划填表时,"*" 的状态转移怎么写才不漏情况
DP 状态 dp[i][j] 表示 s[0..i-1] 是否匹配 p[0..j-1]。关键在 p[j-1] == '*' 时的转移:它既能匹配空(继承 dp[i][j-1]),也能匹配至少一个字符(继承 dp[i-1][j])。二者是“或”关系,不是“且”。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 初始化:
dp[0][0] = true;dp[0][j]依赖p[0..j-1]是否全为"*",需预处理 - 循环顺序必须是外层
i(s 长度),内层j(p 长度),否则dp[i-1][j]可能未计算 - 字符匹配条件要严格:
p[j-1] == '?' || s[i-1] == p[j-1],注意不能漏掉'?'分支
std::string::compare 和手写双指针谁更适合简单通配匹配
如果只要支持 "?" 和 "*"、且输入长度不大(),双指针贪心比 DP 更快、内存更省。但它的正确性依赖“最左匹配”策略:遇到 <code>"*" 先尽可能少匹配(即跳过),失败时再回溯增加匹配长度——这本质是带剪枝的 DFS,仍需记录星号位置和已匹配的 s 下标。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 避免纯暴力回溯:用两个变量
star_idx记最近"*"位置,match_idx记该"*"当前覆盖到的 s 位置,每次失配就推进match_idx -
std::string::compare完全不适用——它只做字面量比较,不解析通配符 - 若需正则扩展(如
[a-z]、+),直接上std::regex,但注意 C++11 regex 在 GCC 中性能差、部分特性不支持
测试时最容易忽略的边界组合有哪些
很多实现能过 "a*c" 和 "a?b",但栽在这些组合上:
-
s = "",p = "*"→ 应为true;但若 DP 初始化没处理好dp[0][j],会返回false -
s = "adceb",p = "*a*b"→"*"必须跨段匹配,要求算法能“记住”前面的"*"并在后面复用 -
s = "mississippi",p = "mis*is*p?."→ 注意末尾"?."是两个字符:'?'匹配'p','.'字面量匹配'.'?错——这里'.'就是普通字符,不是正则中的通配,除非你定义了它
真正难的不是逻辑分支,而是对 "*" 的“可变长度+可跳过”特性的状态建模是否彻底。哪怕 DP 表维度对了,转移方程里漏掉 dp[i-1][j] 这一项,整个多字符匹配就垮了。

















