[路飞]_72. 通配符匹配

102 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第4天,点击查看活动详情

同时,我也正在参加「掘金·启航计划」

今天我们来做一下leetcode上面的一道比较经典字符串类动态规划题目44. 通配符匹配,建议做这道题目之前先看下10. 正则表达式匹配这道题目

题意

image.png

题目给了我们一个字符串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位用p0-j位匹配成功的条件就是 s的0-i-1位用p0-j位匹配成功
        // 如果*匹配两个字符,那么s的0-i位用p0-j位匹配成功的条件就是 s的0-i-2位用p0-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];
         }
      

至此,我们就分析完了这道题目

代码实现

image.png

结束语

如果有更好的分析思路,欢迎大家在评论区发表看法!⛄