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

本文介绍一种使用动态规划解决正则表达式匹配问题的方法,包括定义数组元素含义、找出数组元素间的关系式及确定初始值等关键步骤。

题目

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

示例 1:

输入:
s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。

示例 2:

输入:
s = "aa"
p = "a*"
输出: true
解释: 因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。

示例 3:

输入:
s = "ab"
p = ".*"
输出: true
解释: ".*" 表示可匹配零个或多个('*')任意字符('.')。

示例 4:

输入:
s = "aab"
p = "c*a*b"
输出: true
解释: 因为 '*' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。因此可以匹配字符串 "aab"。

示例 5:

输入:
s = "mississippi"
p = "mis*is*p*."
输出: false

    s 可能为空,且只包含从 a-z 的小写字母。
    p 可能为空,且只包含从 a-z 的小写字母以及字符 . 和 *,无连续的 '*'。


思路:动态规划

动态规划:就是利用历史记录避免重复计算。而这些历史记录,需要一些变量来保存,一般是用一维数组或者二维数组来保存。

动态规划做题三个步骤:

Step1.定义数组元素的含义。

我们会用一个数组,来保存历史记录,一个非常重要的点就是规定这个数组元素的含义。例如 dp[i] 是代表什么意思?


Step2.找出数组元素之间的关系式。

动态规划有一点类似于数学归纳法,当要计算 dp[n] 时,可以利用 dp[n-1],dp[n-2]…..dp[1]来推出 dp[n] 。也就是可以利用历史数据来推出新的元素值,所以我们要找出数组元素之间的关系式,例如 dp[n] = dp[n-1] + dp[n-2],这一步,也是最难的一步。

(学过动态规划的可能都经常听到最优子结构:把一个规模比较大的问题分成几个规模比较小的问题,可以从子问题的最优结果推出更大规模问题的最优结果。例如:让你算每个班的最优成绩就是子问题,你知道所有子问题的答案后,就可以借此推出全校学生的最优成绩这个规模更大的问题的答案。)

Step3.找出初始值

第二步虽然知道了数组元素之间的关系式,例如 dp[n] = dp[n-1] + dp[n-2],可以通过 dp[n-1] 和 dp[n-2] 来计算 dp[n],但得知道初始值啊,例如一直推下去的话,会由 dp[3] = dp[2] + dp[1]。而 dp[2] 和 dp[1] 是不能再分解的了。所以必须要能够直接获得 dp[2] 和 dp[1] 的值,这就是所谓的初始值。(注意初始值的严谨性)有了初始值,并且有了数组元素之间的关系式,那么就可以得到 dp[n] 的值了,而 dp[n] 的含义是由你来定义的,你想求什么,就定义它是什么,这样题目也就解出来了。

1.定义数组元素的含义

dp[i][j]:表示当字符串s和p的长度为i和j时,s与p是否匹配。

我们要求的就是当字符串s长度为n,字符串p长度为m时,s与p是否匹配(false/true)。

2.找出数组元素之间的关系式

通过比较s[i]和p[j],来找出它们之间的关系式。分情况讨论:

①当p[j]是"普通字符"时:

1)当p[j] == s[i]时:dp[i][j] = dp[i - 1][j - 1];

2)当p[j] != s[i]时:dp[i][j] = false;

②当p[j]是" . "(万能符,什么都能匹配)时: dp[i][j] = dp[i - 1][j - 1];(可与①1合并)

③当p[j]是" * "(表示0个或多个前面的字符)时:

1)当p[j - 1] != s[j]时:dp[i][j] = dp[i - 1][j - 2];

2)当p[j - 1] == s[j]时:

  1. 当" * "匹配0个时:dp[i][j] = dp[i - 1][j - 2];
  2. 当" * "匹配1个时:dp[i][j] = dp[i - 1][j - 1];
  3. 当" * "匹配多个时:dp[i][j] = dp[i - 1][j];

3.找出初始值

不能让dp[i][j]越界。

先要找出dp[0][j]的所有值。

dp[0][0] = true;

dp[0][1] = false;

dp[0][j] (j >= 2):

  • 当p[j]不是" * "时,为false;
  • 当p[j]是" * "时,dp[0][j] = dp[0][j - 2];

代码

class Solution {
    public boolean isMatch(String s, String p) {
        if(s == null || p == null) {
            return true;
        }

        int n = s.length();
        int m = p.length();

        boolean[][] dp = new boolean[n + 1][m + 1];

        dp[0][0] = true;
        for(int j = 2; j <= m; j++) {
            if(p.charAt(j - 1) == '*') {
                dp[0][j] = dp[0][j - 2];
            }
        } 
        
        for(int i = 1; i <= n; i++) {
            for(int j = 1; j <= m; j++) { //此处j = 1不用担心下面j - 2会越界(因为就不会走到下面的逻辑)
                //当j不为*
                if(p.charAt(j - 1) != '*') {
                    if(p.charAt(j - 1) == '.' || p.charAt(j - 1) == s.charAt(i - 1)) {
                        dp[i][j] = dp[i - 1][j - 1];
                    }
                } else {
                    //第j - 1个字符不匹配
                    if(p.charAt(j - 2) != s.charAt(i - 1) && p.charAt(j - 2) != '.') {
                        dp[i][j] = dp[i][j - 2];
                    } else {
                        dp[i][j] = dp[i][j - 2] || dp[i][j - 1] || dp[i- 1][j];
                    }
                }
            }
        }
        return dp[n][m];
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值