题目
输入两个字符串 s
,p
,判断两字符串是否匹配。规则如下:
s 仅能包含26个小写字母
a-z
或者是空字符串
p 仅能包含26个小写字母和字符.
,*
.
匹配任何单一字符
*
指定与它相邻的前一个字符的出现次数,可以是0次也可以是无数次
样例(先输入的是s,再输入p)
输入:
aa
a*
输出:
true
输入:
mississippi
mis*is*p*
输出:
false
分析
字符串匹配问题我第一个想到的便是KMP算法,但是KMP算法的匹配是基于字符的逐个比对,不像正则表达式匹配,*
涉及到字符串的匹配。而且在一遇到*
的时候,我们并不能直接确定这个*
代表前一个字符出现几次合适,是要结合前后字符的情况考虑的。因此可以看作是一个动态规划的过程。
状态转移:
设dp(i, j)是一个布尔值,代表p的前i长度的子串是否与s的前j长度的子串相匹配。
显然 dp(0, 0) = true,两个空字符串必定相匹配
if p的第i个字符与s的第j个字符相同,或者p的第i个字符为·(即p的第i个字符与s的第j个字符相配)
dp(i, j) = dp(i-1, j-1)
else if p的第i个字符为*
在这种情况下,如果p的第i-1个字符与s的第j个字符相配(代表p的第i-1个字符的重复次数可以为0,也可以不为0)
dp(i, j) = dp(i-2, j) || dp(i-2, j-1) || dp(i, j-1)
重复0次 || 重复最后一次 || 继续增加重复次数
如果p的第i-1个字符与s的第j个字符不相配(那么重复次数只能为0)
dp(i, j) = dp(i-2, j)
else
dp(i, j) = false.
代码实现
public class Regular_Expression_Matching {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String s = in.next();
String p = in.next();
boolean result = isMatch(s,p);
System.out.println(result);
}
public static boolean isMatch(String s, String p) {
char[] ss = s.toCharArray();
char[] pp = p.toCharArray();
boolean[][] dp = new boolean[pp.length+1][ss.length+1];
//初始化
dp[0][0] = true;
for (int j = 1; j <= ss.length; j++){
dp[0][j] = false;
}
//动规过程
for (int i = 1; i <= pp.length; i++){
for (int j = 0; j <= ss.length; j++){
//对j = 0的单独处理,防止直接动规造成的 ArrayOutOfBound
if (j == 0){
if (pp[i-1] == '*'){
dp[i][0] = dp[i-2][0];
}else{
dp[i][0] = false;
}
}
//状态转移
else {
if (pp[i - 1] == ss[j - 1] || pp[i - 1] == '.') {
dp[i][j] = dp[i - 1][j - 1];
} else if (pp[i - 1] == '*') {
if (i - 2 >= 0 && (pp[i - 2] == ss[j - 1] || pp[i - 2] == '.')) {
dp[i][j] = dp[i][j - 1] || dp[i - 2][j - 1] || dp[i - 2][j];
} else {
dp[i][j] = dp[i - 2][j];
}
} else {
dp[i][j] = false;
}
}
}
}
return dp[pp.length][ss.length];
}
}