lintcode上面有一个192题,通配符匹配问题,描述是这样的:
Test192 : 判断两个可能包含通配符“?”和“*”的字符串是否匹配。匹配规则如下: '?' 可以匹配任何单个字符。 '*' 可以匹配任意字符串(包括空字符串)。 两个串完全匹配才算匹配成功。 函数接口如下: bool isMatch(const char *s, const char *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
这个题看到以后我第一时间想到的是用递归,思路是这样的:
字符串s和匹配串p挨个做字符的匹配,当p出现*的时候,有两种情况:
(1)s向后移一位字符,然后再与剩下的p做匹配;
(2)s向后移一位字符,p向后移一位,然后s和p再进行匹配;
只要(1)和(2)有一种情况匹配,就算成功了。
代码如下:
/** * @param s: A string * @param p: A string includes "?" or "*" * @return: A boolean */ public static boolean isMatch(String s, String p) { if(s == null || p == null){ return false; } return isMatch(s.toCharArray(),p.toCharArray(),0,0); } private static boolean isMatch(char[] cs,char[] ps,int csIndex,int psIndex){ //如果p匹配到结尾了 if(psIndex == ps.length){ return csIndex == cs.length; } //如果匹配字符比原字符长,那么返回false if(cs.length - csIndex < ps.length - psIndex){ return false; } //如果p只剩一个字符了 if(psIndex == ps.length - 1){ if(ps[psIndex] == '*' || (csIndex == cs.length - 1 && (ps[psIndex] == '?' || ps[psIndex] == cs[csIndex]))){ return true; } return false; } //如果c只剩一个字符了 if(csIndex == cs.length - 1){ if(psIndex != ps.length - 1){ return false; } if(ps[psIndex] == '?' || ps[psIndex] == '*' || ps[psIndex] == cs[csIndex]){ return true; } return false; } //如果P还有字符 if(ps[psIndex] == cs[csIndex] || ps[psIndex] == '?'){ return isMatch(cs,ps,csIndex + 1,psIndex + 1); }else if(ps[psIndex] != '*'){ // cache.get(csIndex).set(psIndex,0); return false; }else{ //两种情况,p向后移动一位,或者不移动,两种情况有一种匹配到了就可以 //p应该向后移动多少位(为了去掉多个*在一块的情况) int pIndex = psIndex; while(ps[pIndex] == '*' && pIndex < ps.length){ pIndex++; } return isMatch(cs,ps,csIndex + 1,psIndex) || isMatch(cs,ps,csIndex + 1, pIndex); } }
这种方法肯定是没问题的,但是在提交的最后超时了,超时参数为:
"abbabaaabbabbaababbabbbbbabbbabbbabaaaaababababbbabababaabbababaabbbbbbaaaabababbbaabbbbaabbbbababababbaabbaababaabbbababababbbbaaabbbbbabaaaabbababbbbaababaabbababbbbbababbbabaaaaaaaabbbbbaabaaababaaaabb", "**aa*****ba*a*bb**aa*ab****a*aaaaaa***a*aaaa**bbabb*b*b**aaaaaaaaa*a********ba*bbb***a*ba*bb*bb**a*b*bb"
在我的电脑上执行时间是1359ms
这种方法不行。这时候就想到了:用递归能完成的代码,大多数都能用动态规划完成。动态规划应该要比递归效率高点。
前面做过很多动态规划的例子,有经验了,还是一步一步来:
(1) 找出题目要求的状态表示。题目要求出字符串s和匹配串p是否匹配成功。既然要分成小问题,那么s和p肯定要分成字符数组,分别比较字符(递归也是这么做的);所以状态表示可以想到:两位数组result[][],其中[i][j]表示s的前i个字符和p的前[j]个字符是否匹配成功。
(2)找出状态转换方程,就是说找出当result[i][j]和result[i-1][j - x](其中x表示正整数或0,表示p的前j个字符)的关系。很容易想到,这取决于s的第i个字符和p的第j个字符的关系。
(3)初始化状态数组。应该初始化状态数组result,初始化所有的result[0][j]和result[i][0];
代码:
/** * dpMatch: 动态规划解决匹配字符串的问题 * @param s * @param p * @return * boolean 返回类型 */ private static boolean dpMatch(String s,String p){ if(s == null || p == null){ return false; } //单独处理字符s为空的情况 if(s.length() == 0){ if(p.length() == 0){ return true; } int j = 0; while(j < p.length() && p.charAt(j) == '*'){ j++; } return j == p.length(); } //数组,保存动态规划的结果 boolean[][] result = new boolean[s.length()][p.length()]; //首先初始化结果数组 init(result, s, p); for(int i = 1; i < s.length(); i++){ for(int j = 1; j < p.length(); j++){ if(s.charAt(i) == p.charAt(j) || p.charAt(j) == '?'){ result[i][j] = result[i - 1][j - 1]; }else if(p.charAt(j) == '*'){ result[i][j] = result[i - 1][j] || result[i][j - 1]; } } } return result[s.length() - 1][p.length() - 1]; } /** * init: 初始化数组 * @param result * @param s * @param p * void 返回类型 */ private static void init(boolean[][] result,String s,String p){ //首先初始化第一个匹配传和第一个字符串的匹配情况 if(s.charAt(0) == p.charAt(0) || p.charAt(0) == '?' || p.charAt(0) == '*'){ result[0][0] = true; } //初始化第一个字符串中字符和前j个匹配串的匹配情况 for(int j = 1; j < p.length(); j++){ if(p.charAt(j) != '*'){ break; } result[0][j] = true; } //初始化第一个匹配串中字符和前i个字符串中字符的匹配情况 if(p.charAt(0) == '*'){ for(int i = 1; i < s.length(); i++){ result[i][0] = true; } } }
测试:
long start = System.currentTimeMillis(); System.out.println(dpMatch("abbabaaabbabbaababbabbbbbabbbabbbabaaaaababababbbabababaabbababaabbbbbbaaaabababbbaabbbbaabbbbababababbaabbaababaabbbababababbbbaaabbbbbabaaaabbababbbbaababaabbababbbbbababbbabaaaaaaaabbbbbaabaaababaaaabb", "**aa*****ba*a*bb**aa*ab****a*aaaaaa***a*aaaa**bbabb*b*b**aaaaaaaaa*a********ba*bbb***a*ba*bb*bb**a*b*bb")); long end = System.currentTimeMillis(); System.out.println(end - start);
结果:
false 5
比递归快的不止一点点。。。