LeetCode 10. 正则表达式匹配

107 阅读3分钟

10. 正则表达式匹配

第二个困难题目. 自己解题的思路太"线性", 过于简单, 这篇题解感觉不错. 简洁明了, 补充一下注释, 贴这里.

为了使注释更清楚的表达, 将或与操作改成了if-eles, 原版会更简洁一些.


public class Solution {
    public boolean isMatch(String s, String p) {
        // 使用二维数组, 存储s和p的所有匹配状态.
        // dq[i+1][j+1]==true 表示s的前i个字符和p的前j个模式是匹配的.
        // 因为是前"n个", 所以完整匹配到最后, 需要最大长度是length+1
        // [i+1]*[j+1]的二位数组, 可以包含s和j所有匹配结果.
        // dq[s.length()+1][p.length()+1], 就是s所有字符和 p所有模式的匹配结果. 即最终结果.
        boolean[][] dq = new boolean[s.length() + 1][p.length() + 1];

        // 初始化p中所有模式所对应的起始状态dq[0][0~n]
        for (int j = 0; j <= p.length(); j++) {
            if(j == 0){
                // dq[0][0] 置true  应对".*"开头的情况
                dq[0][j] = true;
            }else if(j > 1 && p.charAt(j - 1) == '*' && dq[0][j - 2]){
                // .* 或者 a*等, 初始值为true, 表示匹配
                // 若果p的第一个就是*, 那么j-2时会异常, 属于正常逻辑
                dq[0][j] = true;
            }else{
                // 其他情况, 初始值默认不匹配
                dq[0][j] = false;
            }

        }

        // 以内层循环j为行, 外层循环i为列, 这两个for循环, 可以理解为:
        // s字符串中, 前i个字符, 对p中前j个模式的匹配结果. 例如:
        // s="abcd" p="a*", i=4, p=2 时, 求的是"abcd"和"a*"是否匹配. 结果保存在dq[4][2]里
        //
        // 使用二位数组存储所有匹配结果, 是为了解决*引发的多个匹配分支问题, 例如:
        // s="abcd"和 p="a*b*", 逐次匹配到"abcd"和"a*b"时, 匹配失败, 分支结果存在dq[4][3]
        // 但是继续比较"abcd" "a*b*", 结果成功匹配的. 分支结果存在dq[4][4]
        //
        // 那么为什么不直接计算dq[4][4]? 而是要计算二维数组中每个位置的值?
        // 因为实际在s中, 前n个字符匹配的前提是: 前n-1个字符也匹配.
        // 因此需要计算保存二位数组中所有位置的值, 用来确认前n-1个字符所有可能匹配的分支.
        //
        // 从1开始, 因为是求前n-1的匹配状态. 从1开始才有意义, 另外dq[0][n]已经存储了初始化的状态.
        for (int i = 1; i <= s.length(); i++) {
            for (int j = 1; j <= p.length(); j++) {
                if (p.charAt(j - 1) == '*') {
                    // 当p是*时
                    if(dq[i][j - 2]){
                        // [j-2]已经匹配, 则当前也匹配,
                        // 例如a*, 当a匹配成功, 则*可以匹配任意字符.
                        dq[i][j] = true;
                    }else if((dq[i - 1][j] && Match(p.charAt(j - 2), s.charAt(i - 1)))){
                        // [j-2]不匹配, 例如"ab*" "aaaa"的情况, 如果:
                        // 1. s中前一个字符与当前阶段的p 已经匹配, 即dq[i-1][j]==true
                        // 2. 判断p的前一个模式(因为当前模式p.charAt(j-1)=='*'), 与s当前的字符匹配.
                        // 则 dq[i][j]匹配
                        //
                        // 为什么先确认dq[i-1][j]的值而不是dq[i-1][j-1]?
                        // 因为当前是匹配'*', 所以实际要先确认上一个字符所在的前(i-1)个字符是否也匹配"*"
                        // 同样是在匹配"*", 所以j的值不变.
                        dq[i][j] = true;
                    }else{
                        dq[i][j] = false;
                    }

                    // 上面的if-else, 可以直接简写成如下形式
                    // dq[i][j] = dq[i][j - 2] || (dq[i - 1][j] && Match(p.charAt(j - 2), s.charAt(i - 1)));
                } else {
                    // 为什么这里是dq[i - 1][j - 1]? 因为当前是字符匹配字符. 需要保证上一个s和上一个p都匹配.
                    dq[i][j] = dq[i - 1][j - 1]
                            && Match(s.charAt(i - 1), p.charAt(j - 1));
                }
            }
        }
        return dq[s.length()][p.length()];
    }

    static boolean Match(char a, char b) {
        return a == b || a == '.' || b == '.';
    }
}