Two strings
这道题是2017年ACM多校训练赛的题目,是一位ACM室友告诉我的,我觉得蛮好玩,就做了一下。
2017 Multi-University Training Contest – Team 9
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6170
题目大意就是给你一个字符串,再一个模式字符串,问你是不是能匹配。
这一听不就是正则表达式做的事情嘛……
模拟是可以做的,不过代码量比较大,需要注意的细节也比较多。
听说,“正解”是动态规划,场上过了的大多数都是DP过的,其他企图瞎分类讨论的大多WA了、企图用正则的时候却发现不怎么会写正则……
DP的思路是维护一个Boolean的二维数组,表示第二个字符串长度为x的情况下,且第一个字符串长度为y,能够匹配。
具体可以参考这篇文章:https://blog.csdn.net/jaihk662/article/details/77483118,以及,https://blog.csdn.net/weyoungg/article/details/77488030,代码量都不小。
我用正则表达式写了一下,代码量少,但是坑很多,主要原因是这道题不是纯粹的正则表达式,对*
和.
的处理是不一样的。
先给出代码。
import java.util.Scanner; import java.util.regex.Matcher; import java.util.regex.Pattern; public class Main { public static void main(String[] args) { Scanner in = new Scanner(System.in); int t = in.nextInt(); while (t-- != 0) { String input = in.next(); String rawRegex = in.next(); rawRegex = rawRegex.replaceAll("(.\\*)\\1{1,}", "$1"); String[] s = rawRegex.split("\\.\\*"); StringBuffer sb = new StringBuffer(); for (int i = 0; i < s.length; i++) { sb.append(s[i]); if (i < s.length - 1) sb.append("(.)\\" + (i + 1) + "*"); } if (rawRegex.endsWith(".*")) sb.append("(.)\\" + (Math.max(s.length,1)) + "*"); String regex = sb.toString(); Pattern p = Pattern.compile(regex); Matcher m = p.matcher(input); System.out.println(m.matches() ? "yes" : "no"); } } }
然后解释一下。
这道题是正则表达式的题目,不过有点区别,所以我还是看了测试数据以后才把所有情况考虑进去的,一开始我用的是正则表达式直接判断的,不仅超时,还是错的。
主要原因是这道题不是纯粹的正则表达式,对*
和.
的处理是不一样的。
int t = in.nextInt(); while (t--!=0) { String s = in.next(); String regex = in.next(); Pattern p = Pattern.compile(regex); Matcher m = p.matcher(s); if (m.matches()) { System.out.println("yes"); } else { System.out.println("no"); } }
上面这段代码很清楚,就是读入一个input
、读入一个regex
,然后compile
一下,去match
。
注意是match
,而不是find
,要求的是整个字符串匹配,而不是存在部分匹配。
但是这段代码超时,并且答案也是错的。
首先解决错误。
看一种情况:.*
如果是普通的匹配,是可以匹配sdfnjkwe
或者的,因为
.*意思就是任意字符匹配任意多次,可以是空串,或者
...
或者.....
等。
但是在这道题的意思是,.
匹配了一个字母,假设是s
,那么.*
只能匹配到sssss
,因为.*
此时就变成了s*
。
这是这道题和Regex不一样的地方,于是我们要把.*
换成(.)\1*
。
这里的括号是分组,表示.
匹配到的那一个,\1
是反向引用。
于是(.)\1*
在上面的例子上就相当于s*
。
String[] s = rawRegex.split("\\.\\*");
首先把.*
给分出来,因为单独的.
和*
都是不影响的。
for (i = 0; i < s.length; i++) { sb.append(s[i]); if (i < s.length - 1) sb.append("(.)\\" + (i + 1) + "*"); } if (rawRegex.endsWith(".*")) sb.append("(.)\\" + (i + 1) + "*");
然后利用StringBuffer把.*
替换成(.)\$*
,其中$
指得是反向引用的编号,注意到最后一个如果是以.*
结尾的,要加上,因为Split会吞掉。
还有一个问题是会超时。
比如用a*a*a*
去匹配baaaa
,显然瞬间就是不可能的,但是匹配aaaaab
的话就要去看看再说。
于是为了满足贪婪匹配原则,就要不停地尝试,所以当比较短的时候还行,长了就不行了。
比如最后一组测试数据,就会导致TLE。
这时候我们考虑一个问题:a*a*
和a*
是一样的,a*a*a*
和a*
是一样的,即(.*)\1{1,}
只要留下一个就行了。
但是怎么就留下一个呢?
我们可以试着全部去掉,这时候就算全部去完了也不要紧,比如a*a*a*
和a*
是一样的,而a*
是可以匹配空串的,但是去掉以后如果变成空串了。
最后,给一个C++的用正则表达式做的链接:https://blog.csdn.net/xlzhang223/article/details/77487955。