Leetcode-10. 正则表达式匹配

721 阅读5分钟

题目

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

'.' 匹配任意单个字符
'*' 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。

  示例 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

来源:力扣(LeetCode)
链接:leetcode-cn.com/problems/re…
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

结题思路

该题目可以使用动态规划来进行求解。

状态矩阵
使用dp[i][j]来表示s[i]是否与p[j]能够匹配,能则为true,否则为false

我们从字符串s和匹配串p的末尾往前看:

  • 1. 匹配串p的当前字符为字母,或者点
    • 字符串s应该有一个对应的字母来进行比较
    • 如果匹配了,问题就等价于字符串s去除最后一个字母,匹配串p去除最后一个字母或者点
    • 如果不匹配,就说明字符串不匹配 image.png

红色分别为当前p是字母和点的两种情况,此时s的当前字母与p当前值匹配,因此问题可以转化为绿色的情况。

  • 2. 匹配串p的当前字符为*
    • 要将*前面的字符或者点与字符串s的当前值进行比较
    • 2.1 如果不匹配,不能认为匹配失败,因为字母*可以出现0次,所以要略过匹配串中的该字母*,字符串s不动,匹配串p的索引前进两位
    • 2.2 如果字母*匹配,会有两种情况,要将两种情况取或运算的结果:
      • 2.2.1 字母*中的字母出现在字符串s中次数为0,此时应该略过匹配串中的字母*然后继续比较
      • 2.2.2 字母*中的字母出现在字符串s中次数大于0,此时应该将字符串s索引前移一位,匹配串不动,看该字母是否继续会出现

疑问:
这里有一个很关键的问题,处于2.2.1情况,即当匹配串当前为*时,比较前面的字母,如果匹配了,为什么还会有该字母在字符串s中出现次数为0的情况?
原因是虽然当前匹配了,但是有可能当前字符传的字母并不是与匹配串p中的字母*匹配(即字母*出现0次),而是与p中其他的字母或者进行了匹配。
该情况后续会举例说明,现在看不懂也没有关系。

image.png

上图第一行是2.1的情况,即当前为*,前置字母不匹配,因为可能有c*不会出现的情况,所以j前移两位,转换成绿色的情况。

上图第二行左半部是2.2.2的情况,对应公式中红色部分,即当前为*,前置字母匹配,也确实在s中出现了一次,此时应该将i前移一位,继续看a*是否还会出现。

上图第二行右半部是2.2.1的情况,对应公式中蓝色部分,即当前为*,前置字母匹配,但是字母as中出现次数为0,应该将j左移两位,可能有人会问,a不是在s中出现了么,怎么说没有出现?,这是因为s中出现的a,对应的是p中数组下标为0时的a,即图中红色的两个a是对应的,蓝色的a*其实并没有字母与其对应。

状态转移方程 image.png

如何判断是否匹配? image.png

需要注意的点 dp[0][0]表示的是sp都是空串的时候是否匹配,那如果想要看看s整个串和p整个串是否匹配,需要看的是dp[s.length][p.length],因此dp的长度应该是new boolean[s.length+1][p.length+1]
填表的时候,行下标i0s.length,列下标j1s.length,因为表的第0列用不上,所以可以不填写。然后在程序中需要从sp去元素的时候,都应该将循环变量ij进行减1,才是其数组的当前值。

代码

public boolean isMatch(String s, String p) {
    int m = s.length();
    int n = p.length();
    boolean[][] dp = new boolean[m + 1][n + 1];
    dp[0][0] = true;

    for (int i = 0; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            if (p.charAt(j-1) == '*') {
                dp[i][j] = dp[i][j-2];
                if (matches(s, p, i, j-1)) {
                    dp[i][j] = dp[i-1][j] || dp[i][j];
                }
            } else {
                if (matches(s, p, i, j)) {
                    dp[i][j] = dp[i-1][j-1];
                } else {
                    dp[i][j] = false;
                }
            }
        }
    }
    return dp[m][n];
}

private boolean matches(String s, String p, int i, int j) {
    if (i == 0) {
        return false;
    }
    if (p.charAt(j-1) == '.') {
        return true;
    }
    return s.charAt(i-1) == p.charAt(j-1);
}

单元测试

public class Solution_Test_10 {
    Solution_10 solution_10 = new Solution_10();

    @Test
    public void test1() {
        String s = "aa";
        String p = "a";
        boolean match = solution_10.isMatch(s, p);
        assertEquals(match, false);
    }

    @Test
    public void test2() {
        String s = "aa";
        String p = "a*";
        boolean match = solution_10.isMatch(s, p);
        assertEquals(match, true);
    }

    @Test
    public void test3() {
        String s = "ab";
        String p = ".*";
        boolean match = solution_10.isMatch(s, p);
        assertEquals(match, true);
    }

    @Test
    public void test4() {
        String s = "aab";
        String p = "c*a*b";
        boolean match = solution_10.isMatch(s, p);
        assertEquals(match, true);
    }

    @Test
    public void test5() {
        String s = "mississippi";
        String p = "mis*is*p*.";
        boolean match = solution_10.isMatch(s, p);
        assertEquals(match, false);
    }

    @Test
    public void test6() {
        String s = "aaa";
        String p = "ab*a*c*a";
        boolean match = solution_10.isMatch(s, p);
        assertEquals(match, true);
    }
}