前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >剑指Offer题解 - Day64

剑指Offer题解 - Day64

作者头像
chuckQu
发布2022-08-19 11:07:29
1350
发布2022-08-19 11:07:29
举报
文章被收录于专栏:前端F2E

剑指 Offer 19. 正则表达式匹配

力扣题目链接

请实现一个函数用来匹配包含'. ''*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a""ab*ac*a"匹配,但与"aa.a""ab*a"均不匹配。

「示例 1:」

代码语言:javascript
复制
输入:
s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。

「示例 2:」

代码语言:javascript
复制
输入:
s = "ab"
p = ".*"
输出: true
解释: ".*" 表示可匹配零个或多个('*')任意字符('.')。
  • s 可能为空,且只包含从 a-z 的小写字母。
  • p 可能为空,且只包含从 a-z 的小写字母以及字符 .*,无连续的 '*'

分析:

总的思路应该是这样的:

  • 从s[1]和p[1]开始判断,每轮添加一个字符并判断是否能匹配,直至添加完整个字符串s和p。
  • 如果s[1-i]和p[1-i]是匹配的,那么添加下一个字符会出现两种情况:
    • 添加字符s[i + 1]是否匹配?
    • 添加字符p[i + 1]是否匹配?
  • 由此可见,此题可以使用动态规划求解。

问题来到了动态规划转移方程如何确定。

首先,我们规定dp[i][j]代表字符串 s 的前 i 个字符和 p 的前 j 个字符能否匹配。

由于 dp[0][0]代表的是空字符的状态, 因此 dp[i][j]对应的添加字符是 s[i - 1]p[j - 1]dp[i][j] 要想成立,取决于p[j - 1] 是否为* ,因为表示匹配零个或多个字符。

  • p[j - 1] === '*' 时,以下情况为true时,dp[i][j]true
    • dp[i][j - 2] 。那么p[j - 2]p[j - 1] 就相当于p[j - 2]* 。此时*意味着出现0次。那么p[j - 2] 出现0次,就是匹配的。
    • dp[i - 1][j]s[i - 1] === p[j - 2] ****。那么p[j - 2]p[j - 1] 就相当于p[j - 2]p[j - 2] ,此时* 意味着多出现一次,就是匹配的。
    • dp[i - 1][j]p[j - 2] === '.' ****。那么p[j - 2]p[j - 1] 就相当于.. ,此时* 意味着多出现一次,就是匹配的。
  • p[j - 1] !== '*'时,以下情况为true时,dp[i][j]true
    • dp[i - 1][j - 1]s[i - 1] === p[j - 1] 。意味着s[i - 2] === p[i - 2]s[i - 1] === p[i - 1] ,那么此时肯定是相等的。
    • dp[i - 1][j - 1]p[j - 1] = '.' ****。意味着p[j - 1] 可以是任意字符,当然可以是s[i - 1] ,那么此时就是匹配的。

总的来看,通过判断p的最后一个字符是否等于'*' ,来决定如何转移状态。当等于'*' 时,因为可以出现零次或者多次,所以需要判断上个字符p[j - 2]与上一个状态的关系。当不等于'*' 时,需要判断当前字符p[j - 1]s[i - 1] 的关系。

还需要初始化dp矩阵首行,防止状态转移索引越界。

  • dp[0][0] = true。也就是说两个空字符串是可以匹配的。
  • dp[0][j] = dp[0][j - 2]p[j - 1] === '*'。因为首行 s为空字符串,因此当 p 的偶数位为 * 时才能够匹配。此时* 意味着前面的字符出现0次,也就是p的奇数位出现0次。所以将p的偶数位置为*

最后返回矩阵右下角字符即可。

动态规划

代码语言:javascript
复制
/**
 * @param {string} s
 * @param {string} p
 * @return {boolean}
 */
var isMatch = function(s, p) {
    const m = s.length + 1;
    const n = p.length + 1;
    const dp = Array.from({ length: m }, () => Array.from({ length: n }).fill(false));
    dp[0][0] = true;
    for(let j = 2; j < n; j += 2) {
        dp[0][j] = dp[0][j - 2] && p[j - 1] === '*';
    }
    for(let i = 1; i < m; i++) {
        for(let j = 1; j < n; j++) {
            if (p[j - 1] === '*') {
                dp[i][j] = dp[i][j - 2] || dp[i - 1][j] && (s[i - 1] === p[j - 2] || p[j - 2] === '.')
            } else {
                dp[i][j] = dp[i - 1][j - 1] && (s[i - 1] === p[j - 1] || p[j - 1] === '.')
            }
        }
    }
    return dp[m - 1][n - 1];
};
  • 时间复杂度 O(mn)。
  • 空间复杂度 O(mn)。

总结

本题考查动态规划的应用。难度系数是困难。本题的难点在于如果转义状态。通过判断p的最后一个字符是否为* ,衍生出两种状态。

复杂度方面,需要遍历整个dp矩阵,因此时间复杂度是O(mn) ,而dp矩阵需要占用额外的O(mn) 空间。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-03-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端F2E 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 剑指 Offer 19. 正则表达式匹配
    • 动态规划
      • 总结
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档