持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第4天,点击查看活动详情
同时,我也正在参加「掘金·启航计划」
今天我们来做一下leetcode上面的一道比较经典字符串类动态规划题目44. 通配符匹配,建议做这道题目之前先看下10. 正则表达式匹配这道题目
题意
题目给了我们一个字符串s和一个字符模式(类似正则表达式),让我们实现一个支持? 匹配单个字符,*匹配多个字符的匹配逻辑
同样是给了我们两个字符串,同样的要求我们处理匹配的情况,真的是满满的动态规划的味道。🤣
别看题目的标签打的是困难,但是实际难度比另外一道同样的字符串DP的题目剑指 Offer 19. 正则表达式匹配难度小很多
思路
既然是要使用动规进行解答,那么就少不了分析其中的递推过程,也就是我们可以从最简单的情况开始考虑,根据递推 关系解出更大规模的问题。
例如 s=adceb p = *a*b
记:m = s.length,n = p.length
-
状态定义 dp[i][j]表示s前i个字符使用可以使用p前j个字符匹配成功
-
状态初始化
- 默认字符与字符不能成功匹配
let dp = Array(m + 1).fill().map(() => Array(n + 1).fill(0))- 首先,如果s和p都为空,由于空字符串可以匹配空字符串,所以dp[0][0]=1
dp[0][0]=1- 假设s为空,p不为空,那么是否能够匹配成功,取决于p前面有几个连续的*,因为只有*可以匹配空字符,也就是
for (let i = 1; i <= n; i++) { if (p[i - 1] == '*') { dp[0][i] = true; } else { break; } }- 同样,假设p为空,那么匹配一定失败,因为空字符不能匹配任何非空字符,所以这里不进行操作
-
状态转移方程
- 由于*?的特殊性,我们在递推过程中,需要对*进行单独讨论
- 如果
p[j-1]=='*',这里是最棘手的地方,因为*可以匹配任意字符,所以不得不进行讨论- 如果*匹配空字符,那么就需要s的0-i位可以通过p的0-j-1位匹配成功,即
dp[i][j] = dp[i][j - 1]- 如果*匹配非空字符,那么有可能匹配前1个字符,前2个字符...前i个字符,即
// 如果*匹配一个字符,那么s的0-i位用p的0-j位匹配成功的条件就是 s的0-i-1位用p的0-j位匹配成功 // 如果*匹配两个字符,那么s的0-i位用p的0-j位匹配成功的条件就是 s的0-i-2位用p的0-j位匹配成功 // ... dp[i][j] = dp[i-1][j] | dp[i-2][j] | dp[i-3][j] | ... | dp[0][j]- 但是,同时我们又可以得出
dp[i-1][j] = dp[i-2][j] | dp[i-3][j] | ... | dp[0][j],推到过程同上,这样一来我们就得出了dp[i][j] = dp[i-1][j] - 根据上面的分析,我们得出这种情况下的递推公式
if (p[j - 1] == '*') { dp[i][j] = dp[i][j - 1] | dp[i - 1][j]; } - 如果
p[j-1]=='?'或者s[i - 1] == p[j - 1],也就是p[i-1]可以匹配s的最后一位,这时只要s的0-i-1位用p的0-j-1位匹配成功即可,也就是
else if (p[j - 1] == '?' || s[i - 1] == p[j - 1]) { // 末尾相同或者 p末尾是? 则结果等于 dp[i-1][j-1] dp[i][j] = dp[i - 1][j - 1]; } - 如果
- 由于*?的特殊性,我们在递推过程中,需要对*进行单独讨论
至此,我们就分析完了这道题目
代码实现
结束语
如果有更好的分析思路,欢迎大家在评论区发表看法!⛄