LeetCode 正则表达式匹配(10题)dp和递归方法

LeetCode 正则表达式匹配

@author:Jingdai
@date:2020.11.28

题目描述(10题)

给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.''*' 的正则表达式匹配。

  • '.' 匹配任意单个字符
  • '*' 匹配零个或多个前面的那一个元素

提示:

  • s 可能为空,且只包含从 a-z 的小写字母
  • p 可能为空,且只包含从 a-z 的小写字母,以及字符 .*
  • 保证每次出现字符 * 时,前面都匹配到有效的字符

示例1

输入: s = "aa" p = "a*"
输出: true

示例2

输入: s = "ab" p = ".*"
输出: true

思路及代码

这个题目可以用动态规划或递归来解决,思路都差不多,这里先介绍 dp 的方法,然后介绍递归的方法。

  • dp 方法
    首先要明确匹配的含义,这点很重要,这也是很多人第一次做的时候懵逼的原因(我就是)。匹配的含义是正则表达式 p 和字符串 s 以任何一种方式匹配就算匹配。比如字符串 "aa" 和 正则表达式 "a*a*" ,这里有好几种匹配方式,比如第一个 * 表示2个a,第二个 * 表示0 个 a;或者第一个 * 表示1个a,第二个 * 表示1个a。但要注意,只要有任何一种方式使正则表达式 p 和字符串 s 匹配了,那么就算是匹配了,而不用所有的方式都匹配。

    接下来看 dp,如何定义状态呢?我们这里定义 dp[i][j] 表示字符串 s 的前 i 个字母和正则表达式 p 的前 j 个字母是否匹配(注意:ij 不是下标,后同)。

    先看初始状态,首先很容易得到 dp[0][0] 为 true(s 的前0个字母和 p 的前 0 个字母);然后当 j 等于 0 时(p的前0个字母),只有当 i 等于0时 dp[i][j] 为true,否则都为false。但是当 i 等于 0 时,dp[i][j] 需要讨论。如图,当 p"a*b*c" 时, dp[0][1] 显然为 false ,但是 dp[0][2] 为true(j = 2 的 * 代表前面有0个a); dp[0][3] 为 false, dp[0][4] 又是 true (j = 4 的 * 代表前面有0个a)。可以得出当 i 等于0时有: dp[i][j] = p.charAt(j-1) == '*' && dp[i][j-2]
    在这里插入图片描述
    下面就是 dp 的重点了,也是这个题的难点,状态转移方程。

    从最简单的开始,如图,当 s 的第 i 个元素(不是下标,后同)和 p 的第 j 个元素(不是下标,后同)相同时(或 p 的第 j 个元素是 '.' ),那么很容易得出 dp[i][j] = dp[i-1][j-1]
    在这里插入图片描述
    s 的第 i 个元素和 p 的第 j 个元素不同且 p 的第 j 个元素为 '*' 时,又分为两种情况。

    1. 如图,当 s 的第 i 个元素和 p 的第 j-1 个元素不同时,那么只有将 p 的第 j-1 个元素和第 j 个元素去掉才有可能使它们相同,故此时 * 只能代表 0 个前面的元素,此时 dp[i][j] = dp[i][j-2]

      在这里插入图片描述

    2. s 的第 i 个元素和 p 的第 j-1 个元素相同时,如图,可以发现不管 i 前有几个相同的元素,如 "cba""cbaa""cbaaa"dp[i][j] 的结果总和 dp[i-1][j] 的结果相同,相当于这里的 ij 都往前移动一位,j 移动代表 a* 少匹配一个 a ,不用动,故得到 dp[i][j] = dp[i-1][j]

      在这里插入图片描述

      但是还有特殊的情况,如图,例如当 s"a" , p 为 "aa*" 时,这时只能删除 a*,才能使 sp 匹配。此时 dp[i][j] = dp[i][j-2] 。前面说过,只要有一种方式匹配成功就算匹配,所以最后的递推式为 dp[i][j] = dp[i-1][j] || dp[i][j-2]

      在这里插入图片描述

综上,所有情况的递推式为:

  1. s 的第 i 个元素和 p 的第 j 个元素相同时:dp[i][j] = dp[i-1][j-1]
  2. s 的第 i 个元素和 p 的第 j 个元素不同且 p 的第 j 个元素为 '*' 时:
    1. s 的第 i 个元素和 p 的第 j-1 个元素不同时:dp[i][j] = dp[i][j-2]
    2. s 的第 i 个元素和 p 的第 j-1 个元素相同时:dp[i][j] = dp[i-1][j] || dp[i][j-2]
  3. 其他情况:dp[i][j] = false

代码如下。

public boolean isMatch(String s, String p) {
     
     

    if (s == null || p == null)
        return false;
    int lenS = s.length();
    int lenP = p.length();

    char[] sChars = s.toCharArray();
    char[] pChars = p.toCharArray();

    boolean[][] dp = new boolean[lenS+1][lenP+1];

    for (int i = 0; i <= lenS; i++) {
     
     
        for (int j = 0; j <= lenP; j++) {
     
     
            if (j == 0) {
     
     
                if (i == 0) {
     
     
                    dp[i][j] = true;
                } // or is false
                continue;
            }
            if (i == 0) {
     
      // j != 0
                if (j - 3 >= 0) {
     
     
                    dp[i][j] = pChars[j-1] == '*' && dp[i][j-2];
                } else {
     
     
                    dp[i][j] = pChars[j-1] == '*'; // the first cant be *
                }
                continue;
            }

            if (sChars[i-1] == pChars[j-1] || pChars[j-1] == '.') {
     
     
                dp[i][j] = dp[i-1][j-1];
            } else if (pChars[j-1] == '*') {
     
     
                if (pChars[j-2] == sChars[i-1] || pChars[j-2] == '.') {
     
     
                    dp[i][j] = dp[i-1][j] || dp[i][j-2];
                } else {
     
     
                    dp[i][j] = dp[i][j-2];
                }
            }
        }
    }

    return dp[lenS][lenP];
}
  • 递归方法
    和 dp 相同,递推的式子完全一样,只要注意递归出口就好。递归出口就是 dp 方法中的初始化部分,如果上面的 dp 方法懂了,递归方法就非常容易看懂,直接上代码。
public boolean isMatch(String s, String p) {
     
     
    if (s == null || p == null) {
     
     
        return false;
    }

    return isMatch(s, p, s.length(), p.length());
}

public boolean isMatch(String s, String p, int firstSLetters, int firstPLetters) {
     
     
    if (firstPLetters == 0) {
     
     
        if (firstSLetters == 0) {
     
     
            return true;
        } else {
     
     
            return false;
        }
    }

    if (firstSLetters == 0) {
     
     
        if (p.charAt(firstPLetters-1) == '*') {
     
     
            return isMatch(s, p, firstSLetters, firstPLetters-2);
        } else {
     
     
            return false;
        }
    }

    if (s.charAt(firstSLetters-1) == p.charAt(firstPLetters-1) 
        || p.charAt(firstPLetters-1) == '.') {
     
     
        return isMatch(s, p, firstSLetters-1, firstPLetters-1);
    } else if (p.charAt(firstPLetters-1) == '*') {
     
     
        if (s.charAt(firstSLetters-1) == p.charAt(firstPLetters-2) 
            || p.charAt(firstPLetters-2) == '.') {
     
     
            return isMatch(s, p, firstSLetters-1, firstPLetters) 
                || isMatch(s, p, firstSLetters, firstPLetters-2);
        } else {
     
     
            return isMatch(s, p, firstSLetters, firstPLetters-2);
        }
    } 
    return false;
}

猜你喜欢

转载自blog.csdn.net/qq_41512783/article/details/110285616