您的位置:首页 > 其它

Leetcode——44Wildcard Matching && 10 Regular Expression Matchi

2017-05-14 16:49 531 查看
本周做的两道题有点类似,一道是通配符匹配问题,一道是正则表达式匹配问题。难度有点大,前者没有用DP或者递归,后者用了DP来实现。

通配符匹配

问题描述

实现一个通配符模式匹配,其中通配符包括‘?’和 ‘ * ’

‘?’可以匹配任意单一字符;
‘ * ’可以匹配任意的字符串(包括空串)
函数isMatch(string s, string p)用于判断字符串s是否能匹配带有通配符的字符串p,举一些例子:

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

思路

一开始想到的是采用贪心的思想,即尽可能多的匹配两个字符串,分为三种情况:(使用变量i表示字符串s的第i个字符,变量j表示字符串p的第j个字符,初始为0)

1. 当s[i]和p[j]相等,或者p[j]是‘?’时,说明匹配成功;

2. 当p[j]不是‘*’,也不是‘?’且s[i]与p[j]不相等时,直接返回错误;

3. 当p[j]是‘*’时,匹配字符串s[i]以及后面的字符,直到p[j+1]与s的第[i+k]个字符相等。

举个例子:s = abeedc  p = ab*c,一开始匹配了a和b,当p[2]是‘*’时,s[2] 与p[3]不相同,所以继续比较s[3]....直到得到s[6]是与p[j+1]相等的,这时候说明p中的‘*’代表了s中的‘eed’子字符串。

 

看上去这种做法是可以的,然后提交之后,结果是WA,问题是:当s = abcgrdrrede   p = ab*de,此时:

如果用上面的解法,p中‘*’代表了s中的“cgr”,后面匹配一个‘d’之后就无法继续匹配了,因此得到的是false;

然而,明显可以看出,如果p中的‘*’代表了子串“cgrdrre”,那么可以匹配的,结果应该是true。

所以,这种方法不适合于上面这种情况。需要采用回溯的方法。

如果记下上一次‘*’出现时i和j的位置,然后当有不匹配出现时,就回到‘*’去执行匹配,每次匹配一个字符后继续往后遍历,一旦发现还是不同,就回到‘*’出现的位置继续下一个匹配,所以‘*’所在的位置要记录,同时,出现‘*’时候的s所在位置也要记录。以s = abcgrdrrede,p = ab*de作为例子:

1)循环遍历s和p,首先是匹配了‘a’和‘b’,当i = 2,j = 2时,p[j]出现‘*’,此时记录出现‘*’的位置ipos = 2,jpos = 2,同时,i要减1,因为此时的s[i]没有和p[j]匹配上,要从j后面的字符找;

2)继续遍历s,i = 2,j = 3,发现不匹配,那么,i回到ipos(2)的位置,j回到jpos(2)的位置,表示通配符‘*’现在代表了s[2]字符,ipos要加1(特别记住)

3)此时i = 3,j = 3,再次比较s[3]和p[3]依旧不匹配,重复以上过程。

4)当i = 5,j = 3时,匹配了字符s[5]的‘d’;此时ipos是4,jpos还是2;

5)接下来i = 6,j = 4,发现不匹配了,就可以回到ipos和jpos的位置,同样重新开始匹配;

6)i = 5,j = 3,又匹配了‘d’,但此时ipos是5,jpos是2;

7)接下来i = 6,j = 4,发现不匹配了,就可以回到ipos和jpos的位置,同样重新开始匹配;

6)i = 6,j = 3,接下来就是按照上面的过程继续进行了。

算法描述:

1.遍历s和p时,当遇到‘*’时,采用两个标志符ipos和jpos记录当前位置;

2.当遇到不匹配的情况时,回溯到‘*’所在的位置,将‘*’代替当前s中不能被p匹配的一个字符,然后继续遍历,直到s遍历结束。

代码

bool isMatch(string s, string p) {
//首先,如果p中没有任何通配符,那直接比较两个字符串即可
if (p.find('?') == p.npos && p.find('*') == p.npos) {
if (p == s)
return true;
else
return false;
}
//ipos和jpos用于记录上一个"*"出现的位置,用于回溯
int i = 0, j = 0, ipos = -1, jpos = -1;
for (i = 0; i < s.length(); i++, j++) {
if (p[j] == '*') {
ipos = i;
jpos = j;
i--;
}
else {
//当发生不匹配时,考虑上一个“*”的位置
if (p[j] != '?' && p[j] != s[i]) {
if (ipos >= 0) {
i = ipos++;
j = jpos;
}
else
return false;
}
}
}
while (p[j] == '*')
j ++;
if (j == p.length())
return true;
else
return false;
}


正则表达式匹配

问题描述

实现正则表达式匹配,其中出现的非通用字符有‘.’和‘ * ’

‘.’可以匹配任意单一字符;
‘ * ’可以匹配零个或多个前缀元素。

函数isMatch(s, p)判断字符串s是否匹配正则表达式p。举些例子:

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


与上一题相比,‘.’相当于上一题的‘?’,不同的是‘*’的表示,这一题表示的是任意个前缀元素。所以例子中的最后一个,“c*”可以表示有0个c或者多个c,所以这里的匹配时true,放在上一题那就是false。

思路

使用动态规划的方法来实现,类似大多数字符串比较问题的DP解法,该子问题dp[i][j]定义为字符串s的前i个字符和字符串p的前j个字符的匹配情况。数组dp[][]是一个bool数组,且大小是(slen + 1) x (plen + 1)

 

我们先假设字符串s和p都是从1到起始的。

 

初始情况:

首先将整个数组初始化为false,接下来考虑什么情况下能初始化为true;
dp[0][0]是true,都是0个字符,当然为true;
对于dp[0][j],也就是s取0个字符,只有p的长度在增长。因为p的第一个字符不可能是‘*’(没有意义),所以只有当j大于1时且p的第j个数,即p[j]是‘*’时,dp[0][j]取决于dp[0][j-2];dp[0][j-2]表示不取‘*’的前缀元素时,能否匹配。

例子:p = a*b,当s取空,p在‘*’可以取0个a,此时dp[0][2]是true的。

状态转移:

1. 若p[j]是‘*’:

要么不取p[j]这个字符,则dp[i][j] = dp[i][j-2];
要么取p[j]这个字符,那么dp[i][j]取决于s的前i-1个字符与p的前j个字符匹配以及第i个字符和第j-1个字符的匹配情况,即dp[i][j] = dp[i-1][j] && (s[i] == p[j-1] || p[j-1] = ‘.’)

2. 若p[j]不是‘*’:

dp[i][j]取决于s的前i-1个字符和p的前j-1个字符的匹配情况,以及s的第i个字符和p的第j个字符的匹配情况,即dp[i ][j] = dp[i - 1][j - 1] && (s[i] == p[j] || '.' == p[j]);

在代码实现时,要注意字符串s和p实际上是从0开始的,所以处理时要注意s和p取字符时的下标要减1.

代码:

if (s.length() == 0 && p.length() == 0)
return true;
if (p.empty())
return false;

//首先,如果p中没有任何通配符,那直接比较两个字符串即可
if (p.find('.') == p.npos && p.find('*') == p.npos) {
if (p == s)
return true;
else
return false;
}

int slen = s.length();
int plen = p.length();
bool dp[slen+1][plen+1]; //表示s的前i个字符和p的前j个字符是否匹配
memset(dp, false, sizeof(dp));
dp[0][0] = true;

int i = 0, j = 0;
for (int j = 2; j <= plen; j++)
if (p[j - 1] == '*')
dp[0][j] = dp[0][j - 2];

for (i = 1; i <= slen; i++) {
for (j = 1; j <= plen; j++) {
// dp[i][j - 2]表示‘*’前的字符取0个;
//或者
if (p[j - 1] == '*')
dp[i][j] = dp[i][j - 2] || (s[i - 1] == p[j - 2] || p[j - 2] == '.') && dp[i - 1][j];
else
dp[i ][j] = dp[i - 1][j - 1] && (s[i - 1] == p[j - 1] || '.' == p[j - 1]);
}
}
return dp[slen][plen];


总结:

第一道题本来想用DP的,结果发现好像写起来比较麻烦= =结果就偷懒写成了回溯的形式。

第二道题,在进行数组dp计算前,加上没有通配符时,直接比较字符串的情况,使得时间一下子少了10ms,当然这跟测试用例也有关。但是我觉得这个判断也是有必要的,毕竟DP的过程耗费时间。

总的来说,虽然做了好几周DP的题目,比以前刚接触时好很多,但有时思路还是难以打开....还需努力啊
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息