LeetCode 10 Regular Expression Matching
方法:递归+memo
时间复杂度:O(mn)
空间复杂度:O(mn)
想法:其实DP和递归+memo都行,但是我比较喜欢写成递归记忆化的形式。canMatchHelper这个函数就是说从字符串s的ts这个地方到最后,与字符串p的tp这个地方到最后,能不能匹配。这个主要是看p字符串,p字符串当中可能有.和*,如果一个字符后面跟着*,那就可以匹配任意数量的这个字符。所以假设说我们遍历到一个地方tp,然后tp+1这个位置是*的话,那么,p字符串展开之后,这个*要么让它前面的字符重复0次,即不出现;要么重复多次。所以这种情况下,如果能够匹配,那就是说要么canMatchHelper(s, ts, p, tp + 2, memo, visited)(对应前面的字符直接删掉),要么就是*前面的字符跟s对应的字符能匹配上单个字符,然后看一下p从tp开始到最后能不能匹配上s剩下的字符。
如果tp+1不是*就更加简单了,直接看一下第一个位置能不能匹配上,如果能的话,ts和tp各往后挪一位继续递归即可。然后研究一下递归过程会发现会有重复调用,因此用memo。
代码:
class Solution {
public boolean isMatch(String s, String p) {
int m = s.length(), n = p.length();
boolean[][] visited = new boolean[m][n];
boolean[][] memo = new boolean[m][n];
return canMatchHelper(s, 0, p, 0, memo, visited);
}
private boolean canMatchHelper(String s, int ts, String p, int tp, boolean[][] memo, boolean[][] visited) {
if (tp == p.length()) {
return ts == s.length();
}
if (ts == s.length()) {
return canMatchEmpty(p, tp);
}
if (visited[ts][tp]) {
return memo[ts][tp];
}
boolean res = false;
if (tp + 1 < p.length() && p.charAt(tp + 1) == '*') {
res = canMatchHelper(s, ts, p, tp + 2, memo, visited) || (canMatchFirst(s, ts, p, tp) && canMatchHelper(s, ts + 1, p, tp, memo, visited));
}
else {
res = canMatchFirst(s, ts, p, tp) && canMatchHelper(s, ts + 1, p, tp + 1, memo, visited);
}
visited[ts][tp] = true;
memo[ts][tp] = res;
return res;
}
private boolean canMatchFirst(String s, int ts, String p, int tp) {
return p.charAt(tp) == '.' || s.charAt(ts) == p.charAt(tp);
}
private boolean canMatchEmpty(String p, int tp) {
for (int i = tp; i < p.length(); i += 2) {
if (i + 1 == p.length() || p.charAt(i + 1) != '*') {
return false;
}
}
return true;
}
}
LeetCode 44 Wildcard Matching
方法1:递归+memo
时间复杂度:O(mn)
空间复杂度:O(mn)
想法:这个就是跟上一道题差不多的思路,反正还是一个递归+memo。这个题的?相当于上一题的.,这个倒是比较容易处理,但是跟上一问不一样的就在于*所代表的作用。这个题里面*能匹配所有字符串,它既能匹配"",也能匹配"aa", "aaa"这种,甚至也能匹配"abcdxyz"。因此这里就跟tp+1这一位是什么没关系了,如果说tp这一位指向的是*的话,那么能匹配上的条件就是canMatchHelper(s, ts, p, tp + 1, memo, visited) || canMatchHelper(s, ts + 1, p, tp, memo, visited)。就是说这个星号要么匹配一个空字符串拉倒,要么匹配掉一个s里面的字符,然后继续往后看。对于tp不是指向星号的情况一如既往地好处理。
代码:
class Solution {
public boolean isMatch(String s, String p) {
int m = s.length(), n = p.length();
boolean[][] memo = new boolean[m][n];
boolean[][] visited = new boolean[m][n];
return canMatchHelper(s, 0, p, 0, memo, visited);
}
private boolean canMatchHelper(String s, int ts, String p, int tp, boolean[][] memo, boolean[][] visited) {
if (tp == p.length()) {
return ts == s.length();
}
if (ts == s.length()) {
return canMatchEmpty(p, tp);
}
if (visited[ts][tp]) {
return memo[ts][tp];
}
boolean res = false;
if (p.charAt(tp) == '*') {
res = canMatchHelper(s, ts, p, tp + 1, memo, visited) || canMatchHelper(s, ts + 1, p, tp, memo, visited);
}
else {
res = canMatchFirst(s, ts, p, tp) && canMatchHelper(s, ts + 1, p, tp + 1, memo, visited);
}
memo[ts][tp] = res;
visited[ts][tp] = true;
return res;
}
private boolean canMatchFirst(String s, int ts, String p, int tp) {
return p.charAt(tp) == '?' || s.charAt(ts) == p.charAt(tp);
}
private boolean canMatchEmpty(String p, int tp) {
for (int i = tp; i < p.length(); i++) {
if (p.charAt(i) != '*') {
return false;
}
}
return true;
}
}
方法2:双指针
时间复杂度:O(mn)
空间复杂度:O(1)
想法:这个是从LeetCode高赞解答的一个评论里面leetcode.com/problems/wi… 看过来的。就是说starJ记录p字符串内*的位置,然后i会指向之后一个match上的s字符串中的下标位置。基本上就是说,我们两个指针,然后开始扫,然后如果s对应的字符和p对应的字符能够匹配得上,p不是星号,那么这个地方就匹配上了,i和j各往后走一步。如果p对应的是*,那就把这个地方记一下,lastMatchI记此时s的下标,starJ记此时j的下标。然后如果这俩if都不进去,那就是说这个i和j对应的字符匹配不上,匹配不上就要试图把前面p字符串里面的*拿出来救火,试图回到当时那个状态,然后跟上次相比,让*多去匹配一个s里面的字符串。如果i和j对应的既匹配不上,前面又没有*,那就GG了,return false。
代码:
class Solution {
public boolean isMatch(String s, String p) {
int m = s.length(), n = p.length();
int i = 0, j = 0;
int lastMatchI = -1, starJ = -1;
while (i < m) {
if (j < n && (p.charAt(j) == '?' || s.charAt(i) == p.charAt(j))) {
i++;
j++;
}
else if (j < n && p.charAt(j) == '*') {
starJ = j++;
lastMatchI = i;
}
else if (starJ >= 0) {
i = ++lastMatchI;
j = starJ + 1;
}
else {
return false;
}
}
while (j < n) {
if (p.charAt(j) != '*') {
return false;
}
j++;
}
return true;
}
}