使用动态编程了解正则表达式字符串匹配

时间:2018-03-30 09:58:33

标签: java regex algorithm dynamic-programming computer-science

我遇到了这个问题,要求你实现一个支持'。'的正则表达式匹配器。和'*',其中

''匹配任何单个字符。

'*'匹配前面元素中的零个或多个。

isMatch("aa","a") → false
isMatch("aa","aa") → true
isMatch("aaa","aa") → false
isMatch("aa", "a*") → true
isMatch("aa", ".*") → true
isMatch("ab", ".*") → true
isMatch("aab", "c*a*b") → true

虽然我能够以线性方式解决这个问题,但我遇到了许多使用DP的解决方案,如下所示,

class Solution {
    public boolean isMatch(String text, String pattern) {
        boolean[][] dp = new boolean[text.length() + 1][pattern.length() + 1];
        dp[text.length()][pattern.length()] = true;

        for (int i = text.length(); i >= 0; i--){
            for (int j = pattern.length() - 1; j >= 0; j--){
                boolean first_match = (i < text.length() && 
                                       (pattern.charAt(j) == text.charAt(i) ||
                                        pattern.charAt(j) == '.'));
                if (j + 1 < pattern.length() && pattern.charAt(j+1) == '*'){
                    dp[i][j] = dp[i][j+2] || first_match && dp[i+1][j];
                } else {
                    dp[i][j] = first_match && dp[i+1][j+1];
                }
            }
        }
        return dp[0][0];
    }
}

我很难理解这一点。我已经解决了一些涉及网格的DP问题(2d网格中的最短路径,二进制2d网格中的最大正方形),使用DP表对我来说非常有意义。但是在这里我完全迷失了,我无法理解遍历2d表如何帮助解决这个问题。更进一步看来我们知道字符在循环中不匹配,所以我不明白为什么我们不在那里终止搜索(这也可能是由于我对表遍历导致如何导致一个办法)。对于像这样的问题有明确的直观解释吗?

1 个答案:

答案 0 :(得分:2)

使用DP来解决这样的问题的直觉是找出以下问题的答案

  1. 使用递归可以解决问题吗?这意味着它可以用相同类型的较小子问题来表示吗?
  2. 在递归树中重复出现较小的子问题吗?如果是,则可以以这样的方式存储较小问题的结果:每当遇到类似的子问题时,可以在O(1)中访问结果。这通常称为memoization。
  3. 让我们首先了解问题的解决方案,你可以在线性方式解决时找到解决方案。

    1. 将文字与模式匹配时,第一个字符将匹配或不匹配。

      案例1:第一个字符匹配或模式的第一个字符是'。'

      案例1.1下一个字符是'*'

      案例1.2下一个字符不是'*'

      案例2:第一个字符不匹配

      案例2.1下一个字符是'*'

      案例2.2下一个字符不是'*'

    2. 现在让我们弄清楚前面针对上述问题讨论的两个步骤。

      案例1.1的例子是

      isMatch("a", "a*a")isMatch("aab", "a*b") 这相当于解决

      isMatch("a", "a") || isMatch("", "a*a")

      分别为

      isMatch("aab", "b") || isMatch("ab", "a*b")||条件的第一部分涵盖了模式中可选字符的场景,因为它后跟'*',第二部分涵盖了重复匹配字符的场景。

      同样可以为所有案例形成子问题。案例2.2应该  直接回归虚假。

      下面是带有递归方法的java代码

      public boolean isMatch(String text, String pattern) {
          dp = new Boolean[text.length()][pattern.length()];
          return isMatch(text, pattern, 0, 0);
      }
      
      private boolean isMatch(String text, String pattern, int ti, int pi) {
      
          if (pi == pattern.length() && ti < text.length()) return false;
      
          if (ti == text.length() && pi == pattern.length()) return true;
      
          if (ti == text.length()) {
              return isNextCharStar(pattern, pi) && isMatch(text, pattern, ti, pi + 2);
          }
      
      
          boolean result;
          final boolean hasFirstMatched = text.charAt(ti) == pattern.charAt(pi) || pattern.charAt(pi) == '.';
      
          if (isNextCharStar(pattern, pi)) {
              result = isMatch(text, pattern, ti, pi + 2);
              if (hasFirstMatched) {
                  result = result || isMatch(text, pattern, ti + 1, pi);
              }
              return result;
          }
      
          return hasFirstMatched && isMatch(text, pattern, ti + 1, pi + 1);
      
      }
      
      private boolean isNextCharStar(String pattern, int pi) {
          return pi < pattern.length() - 1 && pattern.charAt(pi + 1) == '*';
      }
      

      现在让我们应用memoization步骤。如果我们在返回之前开始保存结果,我们可以在下次重用它。我们应该如何以及在哪里保存它? 对于tipi的所有可能组合,我们必须存储结果。其中ti为文本索引,pi为模式索引。所以大小为text.length * pattern.length的二维数组就足够了。以下是上述更改后的代码

      Boolean [][] dp;
      
      public boolean isMatch(String text, String pattern) {
          dp = new Boolean[text.length()][pattern.length()];
          return isMatch(text, pattern, 0, 0);
      }
      
      private boolean isMatch(String text, String pattern, int ti, int pi) {
      
          if (pi == pattern.length() ) return ti == text.length();
      
          if (ti == text.length()) {
              return isNextCharStar(pattern, pi) && isMatch(text, pattern, ti, pi + 2);
          }
      
          if(dp[ti][pi] != null) return dp[ti][pi];
      
          boolean result;
          final boolean hasFirstMatched = text.charAt(ti) == pattern.charAt(pi) || pattern.charAt(pi) == '.';
      
          if (isNextCharStar(pattern, pi)) {
              result = isMatch(text, pattern, ti, pi + 2);
              if (hasFirstMatched) {
                  result = result || isMatch(text, pattern, ti + 1, pi);
              }
              dp[ti][pi] = result;
              return result;
          }
      
          dp[ti][pi] = hasFirstMatched && isMatch(text, pattern, ti + 1, pi + 1);
          return dp[ti][pi];
      
      }
      
      private boolean isNextCharStar(String pattern, int pi) {
          return pi < pattern.length() - 1 && pattern.charAt(pi + 1) == '*';
      }
      

      如果您仔细观察,只需更改3行,使其成为递归解决方案的DP解决方案。