Leetcode——44Wildcard Matching && 10 Regular Expression Matchi
2017-05-14 16:49
531 查看
本周做的两道题有点类似,一道是通配符匹配问题,一道是正则表达式匹配问题。难度有点大,前者没有用DP或者递归,后者用了DP来实现。
‘?’可以匹配任意单一字符;
‘ * ’可以匹配任意的字符串(包括空串)
函数isMatch(string s, string p)用于判断字符串s是否能匹配带有通配符的字符串p,举一些例子:
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遍历结束。
‘.’可以匹配任意单一字符;
‘ * ’可以匹配零个或多个前缀元素。
函数isMatch(s, p)判断字符串s是否匹配正则表达式p。举些例子:
与上一题相比,‘.’相当于上一题的‘?’,不同的是‘*’的表示,这一题表示的是任意个前缀元素。所以例子中的最后一个,“c*”可以表示有0个c或者多个c,所以这里的匹配时true,放在上一题那就是false。
我们先假设字符串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.
第二道题,在进行数组dp计算前,加上没有通配符时,直接比较字符串的情况,使得时间一下子少了10ms,当然这跟测试用例也有关。但是我觉得这个判断也是有必要的,毕竟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的题目,比以前刚接触时好很多,但有时思路还是难以打开....还需努力啊
相关文章推荐
- 【LeetCode】Wildcard Matching && Regular Expression Matching
- leetcode 10 Regular Expression Matching & 44 Wildcard Matching
- leetcode[10]Regular Expression Matching
- Java [leetcode 10] Regular Expression Matching
- LeetCode 10: Regular Expression Matching
- [Leetcode 10, Hard] Regular Expression Matching
- [leetcode-10]Regular Expression Matching(C)
- [leetcode] 10 Regular Expression Matching
- LeetCode 10 - Regular Expression Matching
- leetcode-10Regular Expression Matching
- leetcode 10 -- Regular Expression Matching
- leetcode 10: Regular Expression Matching
- [LeetCode]Regular Expression Matching、Wildcard Matching
- LeetCode 10 Regular Expression Matching 正则匹配
- leetcode解题报告:10 Regular Expression Matching
- [Leetcode] 10. Regular Expression Matching
- [*leetcode 10] Regular Expression Matching
- LeetCode(10) Regular Expression Matching
- leetcode Wildcard Matching ,Regular Expression Matching (正则表达式匹配和通配符匹配)
- leetcode 10 -- Regular Expression Matching