因“”可匹配任意长度子串,朴素递归会指数级展开分支且重复计算相同子问题,如模式“*a”配“aaa”时无法剪枝,导致超时;加记忆化、提前终止、避免切片可优化。

为什么 isMatch 递归写法在 "*" 多时会超时
因为 "*" 可匹配任意长度子串,递归中若不剪枝,会指数级展开分支。比如模式 "********a" 配字符串 "aaa",每个 "*" 都尝试匹配 0、1、2…个字符,实际只需让第一个 "*" 吃掉所有 "a",其余 "*" 匹配空串——但朴素递归无法感知这点,反复重复计算相同子问题。
实操建议:
- 加记忆化:用
memo[i][j]记录s[i:]和p[j:]是否匹配过,std::vector<:vector>></:vector>比std::map更快(索引 O(1)) - 提前终止:若
p[j] == '*'且isMatch(s, i, p, j+1)为真,直接返回 true(贪心跳过当前"*"的匹配尝试) - 避免字符串切片传参:传
const string&+ 下标,而非s.substr(i),否则每次调用都构造新字符串
dp[i][j] 状态定义和初始化怎么才不绕晕
定义 dp[i][j] 表示 s[0..i-1](前 i 个字符)是否能被 p[0..j-1](前 j 个字符)匹配。关键点是「长度」而非「下标」,否则边界容易错。
初始化逻辑:
-
dp[0][0] = true:空串匹配空模式 -
dp[0][j](j > 0):仅当p[j-1] == '*'且dp[0][j-1] == true时为 true("*"可吃掉空串) -
dp[i][0](i > 0)恒为 false:非空串无法被空模式匹配
常见错误:把 dp[i][j] 定义成「以 i,j 结尾」,导致转移时下标混乱;或初始化时漏掉连续 "*" 的传递性(如 p = "**",dp[0][2] 必须为 true)。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
"?" 和 "*" 在 DP 转移中的处理差异
"?" 是确定性匹配:只消耗一个字符,对应 dp[i][j] = dp[i-1][j-1];"*" 是非确定性匹配:可匹配 0 个(跳过它:dp[i][j-1])或 ≥1 个(吃掉 s 末字符:dp[i-1][j])。二者不能合并成同一条件判断。
实操要点:
- 必须分开 if 分支:先判
p[j-1] == '*',再判p[j-1] == '?',最后判普通字符相等 -
"*"的转移是或关系:dp[i][j] = dp[i][j-1] || dp[i-1][j],不是且,也不是先 or 后 and - 注意顺序:
dp[i-1][j]依赖上一行同列,所以填表必须按行优先(i 外层,j 内层),否则读到未计算的值
空间优化到 O(n) 时为什么只能压列不能压行
因为 "*" 的转移依赖 dp[i-1][j](正上方)和 dp[i][j-1](左方),若压行(只存当前行),dp[i-1][j] 就丢失了;而压列(只存一列)时,用两个变量维护「上一行当前列」和「当前行前一列」即可推导出当前值。
更实用的做法是用滚动数组:开 vector<vector>> dp(2, vector<bool>(p.size()+1))</bool></vector>,用 i & 1 切换行。这样既省空间(O(m)),又避免单变量易错的状态覆盖问题。
容易被忽略的是:初始化滚动数组的第 0 行(即空串匹配模式前缀)必须每轮都重置,不能只做一次——因为第二轮的「上一行」其实是第一轮算出的「当前行」,逻辑上仍是初始状态。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










