剑指offer-52:正则表达式匹配

题目描述

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

解题思路

做这题的时候,我花了不少时间。一开始我用两个indexes循环来做题,但遇到 ' * ' pattern 的时候总是无法满足所有情况。
最后发现,有两种比较方便的方法来做这题,递归和动态规划。

方法一:递归

递归方法比动态规划更好理解。通过不断的递归,缩小 str 和 pattern 需要判断的长度,来判断最终是否匹配。
根据我的解题思路,递归有两个base case:

  • str 和 pattern 同时匹配完,即 strIdx == str.length && patIdx == pattern.length。此时返回 true。
  • pattern 先匹配完,但 str 还有剩余。此时返回 false。

    (当 str 先匹配完的时候,如果剩余 pattern 全是 ' _* ' 这种形式,仍然返回 true,否则false)

其余情况,让当前所指的 str 与 pattern 对比:

  • str[strIdx] == pattern[patIdx] 或 pattern[patIdx] == ' . ' :
    • 如果 pattern[patIdx + 1] 不等于 ' * ',则 strIdx 和 patIdx 同时加一,后移一项。
    • 如果等于,又会有三种情况:
      • ab | a*ab,patIdx 后移一位,strIdx 保持不变
      • aab | a*b,strIdx 后移一位,patIdx 保持不变
      • ab | a*b, strIdx 和 patIdx 同时后移一位

        (第一种和第二种情况在递归的时候会包含第三种情况,因此不需要单独把这种情况写入代码中)
  • str[strIdx] != pattern[patIdx]:
    • 如果 pattern[patIdx + 1] 不等于 ' * ', 返回 false。
    • 如果 pattern[patIdx + 1] 等于 ' * ',则从 ' * ' 的后一项接着对比,strIdx 不改变。
public class Solution {
    public boolean match(char[] str, char[] pattern) {
        if (str.length == 0 && pattern.length == 0) return true;
        if (pattern.length == 0) return false;
        return match(str, pattern, 0, 0);
    }

    public boolean match(char[] str, char[] pattern, int strIdx, int patIdx) {
        // base case 1
        if (patIdx == pattern.length && strIdx == str.length) {
            return true;
        }

        // base case 2
        if (patIdx == pattern.length) {
            return false;
        }

        if (strIdx == str.length) {
            if (patIdx + 1 < pattern.length && pattern[patIdx + 1] == '*') {
                return match(str, pattern, strIdx, patIdx + 2);
            }
            return false;
        }

        if (str[strIdx] == pattern[patIdx] || pattern[patIdx] == '.') {
            if (patIdx + 1 < pattern.length && pattern[patIdx + 1] == '*') {
                return match(str, pattern, strIdx + 1, patIdx) ||
                        match(str, pattern, strIdx, patIdx + 2);
            } else {
                return match(str, pattern, strIdx + 1, patIdx + 1);
            }
        } else {
            if (patIdx + 1 < pattern.length && pattern[patIdx + 1] == '*') {
                return match(str, pattern, strIdx, patIdx + 2);
            } else {
                return false;
            }
        }
    }
}

方法二:动态规划

动态规划的问题主要是对问题状态的定义状态转移方程的定义
这题我参考以下链接,并根据自己的思路,整理了一份从后往前推导的方法。

https://liweiwei1419.github.io/sword-for-offer/19-正则表达式匹配/

首先生成一个二维状态表,长宽都比 str 和 pattern 多一。设置默认状态 dp[str.length][pattern.length] = true

这里最难处理的问题还是有 ' * ' 的情况。从后往前循环,每一个状态都是当前状态和后一个状态来决定的,最后只需判断 dp[0][0] 的状态即可。整体情况如下:

  • (图片只显示了主要的过程,省略的部分细节)

  • 当前 pattern 为 ' * '时,跳过,只判断非 ' * ' 的 pattern

  • 当 pattern[patIdx + 1] != ' * ' ,并且 str[i] == pattern[j] 或 pattern[j] == '.':

    • 此时情况很简单,当前 dp[i][j] 的状态由dp[i + 1][j + 1] 或者 dp[i + 1][j + 2n +1] (n = 0, 1, 2, ...)决定,如图所示

  • 当 pattern[patIdx + 1] == ' * ' ,有两种情况:

    • str[i] == pattern[j] || pattern[j] == '.'
      这种情况最为复杂,dp[i][j] 状态由 dp[i + 1][j + 2] || dp[i][j + 2] || dp[i + 1][j] 决定,如图所示

    • str[i] 与 pattern[j]不相等
      此时 dp[i][j] 的状态由 dp[i][j + 2]确定, 如图所示

public class Solution {
    public boolean match(char[] str, char[] pattern) {
        if (str.length == 0 && pattern.length == 0) return true;
        if (pattern.length == 0) return false;
        if (str.length == 0) {
            for (int i = 0; i < pattern.length; i += 2) {
                if (!(i + 1 < pattern.length && pattern[i + 1] == '*')) {
                    return false;
                }
            }
            return true;
        }

        boolean[][] dp = new boolean[str.length + 1][pattern.length + 1];
        dp[str.length][pattern.length] = true;
        for (int i = str.length - 1; i >= 0; i--) {
            for (int j = pattern.length - 1; j >= 0; j--) {
                if (pattern[j] == '*') {
                    continue;
                }
                if (j + 1 < pattern.length && pattern[j + 1] == '*') {
                    if (str[i] == pattern[j] || pattern[j] == '.') {
                        dp[i][j] = dp[i + 1][j + 2] || dp[i][j + 2] || dp[i + 1][j];
                    } else {
                        dp[i][j] = dp[i][j + 2];
                    }
                } else {
                    if (str[i] == pattern[j] || pattern[j] == '.') {
                        int j_tmp = j;
                        while (j_tmp + 2 < pattern.length && pattern[j_tmp + 2] == '*') {
                            j_tmp += 2;
                        }
                        dp[i][j] = dp[i + 1][j_tmp + 1] || dp[i + 1][j + 1];
                    }
                }
            }
        }
        return dp[0][0];
    }
}
posted @ 2020-03-03 19:16  我是宵夜  阅读(171)  评论(0编辑  收藏  举报