zoukankan      html  css  js  c++  java
  • [LeetCode] Wildcard Matching

    (Version 1.0)

    这题在LeetCode的标签有Dynamic Programming,但是实际上的能通过OJ的解法好像不应该被称为DP,感觉这个tag貌似比较有欺骗性。一家之见。

    由Regular Expression Matching的解法而来的DP解法探究

    这题在LeetCode中的标签是Dynamic Programming, Backtracking, Greedy和String,做题之前目测解法要比之前的Regular Expression Matching更精巧,不会是那么straightforward的DP。不过受到Regular Expression Matching的启发,打算先用类似(甚至可以说是相同)的解法解一下,代码如下:

     1 public class Solution {
     2     public boolean isMatch(String s, String p) {
     3         boolean[][] match = new boolean[s.length() + 1][p.length() + 1];
     4         match[0][0] = true;
     5         for (int i = 0; i <= s.length(); i++) {
     6             for (int j = 1; j <= p.length(); j++) {
     7                 if (i != 0) {
     8                     char c = p.charAt(j - 1);
     9                     if (c != '*') {
    10                         match[i][j] = match[i - 1][j - 1] && (c == '?' || c == s.charAt(i - 1));
    11                     } else {
    12                         match[i][j] = match[i][j - 1] || match[i - 1][j - 1] || match[i - 1][j];
    13                     }
    14                 } else {
    15                     if (p.charAt(j - 1) == '*') {
    16                         match[0][j] = true;
    17                     } else {
    18                         break;
    19                     }
    20                 }
    21             }
    22         }
    23         return match[s.length()][p.length()];
    24     }
    25 }

    这个答案不出所料地得到了Memory Limit Exceeded的结果,那么试一试不开二维数组,而只开一维数组行不行呢?代码如下:

     1 public class Solution {
     2     public boolean isMatch(String s, String p) {
     3         boolean[][] match = new boolean[2][p.length() + 1];
     4         match[0][0] = true;
     5         for (int j = 0; j < p.length(); j++) { // initialize first row of match
     6             if (p.charAt(j) == '*') {
     7                 match[0][j] = true;
     8             } else {
     9                 break;
    10             }
    11         }
    12         for (int i = 1; i <= s.length(); i++) {
    13             for (int j = 1; j <= p.length(); j++) {
    14                 char c = p.charAt(j - 1);
    15                 if (c != '*') {
    16                     match[1][j] = match[0][j - 1] && (c == '?' || c == s.charAt(i - 1));
    17                 } else {
    18                     match[1][j] = match[1][j - 1] || match[0][j - 1] || match[0][j];
    19                 }
    20             }
    21             System.arraycopy(match[1], 0, match[0], 0, match[0].length);
    22         }
    23         return match[1][p.length()];
    24     }
    25 }

    依然没有通过,得到的是Time Limit Exceeded。说明这题的思路需要不同于Regular Expression Matching那种近乎brutal force的DP。

    通过了OJ的two pointers解法

    仔细思考一下这题的核心所在,发现问题和Regular Expression Matching的核心类似,其实还是'*'到底要match多少个s中的char。'*'可以match 0/1/多个,所以我们需要在遇到'*'时依然要对不同情形进行试探,而在试探失败之后怎么reset到之前的状态就是问题的核心了。这题使用额外的空间会Memory Limit Exceeded,自然会想到那我们只能用two pointer了,于是问题进一步变成了:如果试探失败,怎么reset这两个pointer到应该去的位置,或者说哪里才是它们应该去的位置。如果我们从匹配0个s中的char开始试探,那么“试探失败”其实意味着我们用'*'来匹配的s中的char匹配少了,需要用一个'*'来匹配更多s中的char,所以对于s的指针,应该reset到的位置应该是上一个被'*'匹配的char后面的第一个char,而对于p的指针,因为我们永远从匹配0个开始,所以依然是reset到'*'之后的第一个char,代码如下:

     1 public boolean isMatch(String s, String p) {
     2     int asterisk = -1;
     3     int lastMatch = -1;
     4     int i = 0;
     5     int j = 0;
     6     while (i < s.length()) { // this loop guard is hard to conceive at the first time.
     7         if (j < p.length() && (p.charAt(j) == '?' || p.charAt(j) == s.charAt(i))) { // simple match
     8             i++;
     9             j++;
    10         } else if (j < p.length() && p.charAt(j) == '*') {
    11             asterisk = j;
    12             lastMatch = i;
    13             j++;
    14         } else if (asterisk != -1) { // don't match or j out of bound
    15             // the critical insight here is that here you need to reset i and j to the correct place
    16             // by correct place I mean set j to right after '*' and i to the next char after the last char in s that matches '*'
    17             j = asterisk + 1;
    18             lastMatch += 1;
    19             i = lastMatch;
    20         } else {
    21             return false;
    22         }
    23     }
    24     while (j < p.length() && p.charAt(j) == '*') {
    25         j++;
    26     }
    27     return j == p.length();
    28 }

    这个解法第一次做稍微难以想到的是应该以怎样的出发点去进行s与p的匹配。当p用完时,可能意味着的是我们使用'*'所匹配的s中的char太少了,需要reset j到合适的位置,所以把j也放入while loop的loop guard是不合适的,因为虽然i也会在loop中大量reset,但是一旦i被用完,剩下需要检验的东西就相对简单的多了,而如果使用j做loop guard,那么就很难处理s用完了但是p还没用完的情况。

  • 相关阅读:
    Windows XP中万能断点
    c#运算符 ?
    转神秘的程序员
    经典解决“线程间操作无效
    文件上传
    dowload.aspx
    mail
    js 正则
    新年快乐
    DataTable Compute
  • 原文地址:https://www.cnblogs.com/icecreamdeqinw/p/4324962.html
Copyright © 2011-2022 走看看