(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还没用完的情况。

 

posted on 2015-03-10 02:44  _icecream  阅读(581)  评论(0编辑  收藏  举报