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

来源:互联网 发布:网络维修电脑招聘 编辑:程序博客网 时间:2024/05/16 05:38

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

通配符匹配

问题描述

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

isMatch("aa","a") → falseisMatch("aa","aa") → trueisMatch("aaa","aa") → falseisMatch("aa", "*") → trueisMatch("aa", "a*") → trueisMatch("ab", "?*") → trueisMatch("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;elsereturn 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;}elsereturn false;}}}while (p[j] == '*')j ++;if (j == p.length())return true;elsereturn false;}

正则表达式匹配

问题描述

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

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

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

isMatch("aa","a") → falseisMatch("aa","aa") → trueisMatch("aaa","aa") → falseisMatch("aa", "a*") → trueisMatch("aa", ".*") → trueisMatch("ab", ".*") → trueisMatch("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;elsereturn 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];elsedp[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的题目,比以前刚接触时好很多,但有时思路还是难以打开....还需努力啊


0 0