剑指offer-正则表达式匹配

题目描述

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

问题分析

  • 这题的难点在于如何处理 * 的情况
  • 先从递归尝试,递归函数 match(str, pattern, sIndex, pIndex) 表示 str[pIndex ~ str.length - 1] 能否匹配 pattern[pIndex ~ pattern.length - 1]。这样依靠递归便完成了问题规模的缩小
  • 那么如何寻求递归关系呢?
    • 根据pIndex +1 位置上是否为 ‘*’ 来分类
      • 如果 pIndex 为pattern最后位置,或者 pIndex + 1 位置上不是 ‘*’,那么当前只需比较 s[sIndex] 是否能匹配 p[pIndex] (当然,比较前先对sIndex判断是否越界)。只有满足当前位置字符能匹配,并且后序字符也能匹配时(递归),从当前位置开始的后序字符才算匹配。例如 abc 与 abc 的匹配,abc 与 *bc 的匹配
        sIndex != s.length && (s[sIndex] == p[pIndex] || p[pIndex] == '.') && match(s, p, sIndex + 1, pIndex + 1)
      • 如果pIndex + 1 位置上是 ‘’,那么需要考虑str中从sIndex开始后连续的多个字符与 pattern[pIndex] 相匹配的情况,例如 a*bb 与 aaaab 的匹配。所以需要用循环来枚举 带来的所有可能性,例如 a* 可以与 a,aa,aaa,aaaa匹配,对上述枚举的结果都要进行递归,一旦发现一个匹配的上,便停止枚举。否则一直到指针指向str末尾或者指向不匹配的字符b为止。 但有要注意的一点是,例如对于 a*aaa 与 aaa,可能pIndex位置匹配成功,但是我们有可能不要,*之前的字符 ‘a’ 出现0次才能匹配的上。
        //若pIndex下一个元素为 *,则需要考虑多个s[sIndex]与 p[pIndex]匹配的情况:枚举
        //例如 a*bb 与 aaaaab这种情况,i会最终停留在b的位置处
        int i = sIndex;
        while (i != s.length && (s[i] == p[pIndex] || p[pIndex] == '.')) {
            if (match(s, p, i + 1, pIndex + 2)) {
                return true;
            }
            i++;
        }
        //match(s, p, sIndex, pIndex + 2) 表示 * 之前的元素为0个,比如 a*aaa 与 aaa 进行匹配
        return match(s, p, sIndex, pIndex + 2);
    }
  • 递归的base case 呢?
        //p串已经用尽
        if (pIndex == p.length) {
            //若s也已经用尽,则匹配成功,否则,匹配失败
            return sIndex == s.length;
        }

  • 上述是递归行为,那么如何用动态规划 dp 来优化递归?
  • dp[i][j] 表示 str[i ~ str.length - 1] 能否与 pattern[j ~ pattern.length - 1] 相匹配。经过递归关系可知,dp[i][j] 主要依赖于 dp[i+1][j+1] (当j+1 为 pattern.length 或者 j+1处不为 * 时) 或者 依赖 dp[i][j+2], dp[i+1][j+2],…所以从二维表中可以看出,只依赖于它左边与下边的位置。所以更新dp二维数组的顺序是从右到左,从下到上。
  • 那么如何确定初始条件呢?
    根据递归的base case 可知,初始条件便知道一列即可,除了右下角位置为 true,最后一列都为 false
  • 关于初始条件的填写,有个选择性问题要记住:
    • 初始条件可以根据题意写和递归 base case
    • 如果你初始条件填的很少,那么以后填整张表时,有可能会越界,所以,必须填表时判断越界问题。
    • 如果你初始条件填的很多,那么便不会出现越界问题
    • 例如该题,如果你初始(根据题意)填上最后一行,与最后两列,那么不会出现越界问题。如果只填最后一列,在填最后一行或者倒数第二列的时候,肯定会越界,必须防止越界。

经验教训

  • 如何缩小问题规模(确定递归函数的形式),如何找到递归关系(根据题意),重中之重!!!
  • 改写dp时,初始条件填写的选择性问题

代码实现:

  • 递归法
    public boolean match1(char[] str, char[] pattern) {
        if (str == null || pattern == null) {
            return false;
        }
        return match(str, pattern, 0, 0);
    }

    //match(): 表示s[sIndex~s.length - 1]与 p[pIndex~p.length - 1]是否能匹配得上
    public boolean match(char[] s, char[] p, int sIndex, int pIndex) {
        //p串已经用尽
        if (pIndex == p.length) {
            //若s也已经用尽,则匹配成功,否则,匹配失败
            return sIndex == s.length;
        }
        //pIndex是p最后一个字符,或者 pIndex下一个元素不是'*',则只考虑将s[sIndex]与 p[pIndex]匹配
        if (pIndex + 1 == p.length || p[pIndex + 1] != '*') {
            return sIndex != s.length && (s[sIndex] == p[pIndex] || p[pIndex] == '.') && 
                match(s, p, sIndex + 1, pIndex + 1);
        }
        //若pIndex下一个元素为 *,则需要考虑多个s[sIndex]与 p[pIndex]匹配的情况:枚举
        //例如 a*bb 与 aaaaab这种情况,i会最终停留在b的位置处
        int i = sIndex;
        while (i != s.length && (s[i] == p[pIndex] || p[pIndex] == '.')) {
            if (match(s, p, i + 1, pIndex + 2)) {
                return true;
            }
            i++;
        }
        //match(s, p, sIndex, pIndex + 2) 表示 * 之前的元素为0个,比如 a*aaa 与 aaa 进行匹配
        return match(s, p, sIndex, pIndex + 2);
    }
  • 动态规划法:
    public boolean match(char[] str, char[] pattern) {
        if (str == null || pattern == null) {
            return false;
        }
        boolean[][] dp = new boolean[str.length + 1][pattern.length + 1];
        //初始化矩阵,相当于初始化最后一列
        dp[str.length][pattern.length] = true;
        //boolean[][] dp = initDP(str, pattern);
        for (int i = str.length; i >= 0; i--) {
            for (int j = pattern.length - 1; j >= 0; j--) {
                if (j + 1 == pattern.length || pattern[j + 1] != '*') {
                    dp[i][j] = i != str.length && (str[i] == pattern[j] || pattern[j] == '.') && dp[i + 1][j + 1];
                    continue;
                }
                int index = i;
                while(index < str.length && (str[index] == pattern[j] || pattern[j] == '.')) {
                    if (dp[index + 1][j + 2]) {
                        dp[i][j] = true;
                        break;
                    }
                    index++;
                }
                dp[i][j] = dp[i][j] == true ? true : dp[i][j+2];
            }
        }
        return dp[0][0];
    }

你可能感兴趣的:(剑指offer,剑指offer(Java版))