正则表达式匹配
题目描述:
给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。
'.' 匹配任意单个字符
'*' 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。
提示:
0 <= s.length <= 20
0 <= p.length <= 30
s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母,以及字符 . 和 *。
保证每次出现字符 * 时,前面都匹配到有效的字符
class Solution {
public boolean isMatch(String s, String p) {
// 采用动态规划解决
boolean[][] dp = new boolean[s.length()+1][p.length()+1]; // 默认值全为false,dp[i][j]的解释:对于p的前j个,可匹配s的前i个
// 边界条件
//1.当p为前i个时,匹配s的前0个
// 此时只有一种情况可以使得其中偶数位置的元素为true,且其前面的偶数位置也为ture,以及偶数位置的元素为*时
if(p.length() >= 2) {
if(p.charAt(1) == '*') {
dp[0][2] = true;
}
for(int i = 3 ; i<p.length() ;i++) {
if(i%2 == 1 && p.charAt(i) == '*' && dp[0][i-1]) {
dp[0][i+1] = true;
}
}
}
//2.当p为前0个时,匹配s的前i个
// 此时应该全为false
//3.当p为前0个,s为前0个时
// 此时应该为true
dp[0][0] = true;
// 动态规划过程 这里注意s和p数组下标和dp的数组下标含义不同
for(int i = 1 ; i<=s.length() ; i++) {
for(int j = 1 ; j<=p.length() ; j++){
if((s.charAt(i-1) == p.charAt(j-1)) || (p.charAt(j-1) == '.')){ // s[i] == p[j] || p[j] == '.'
dp[i][j] = dp[i-1][j-1];
}else if(p.charAt(j-1) == '*'){
if((s.charAt(i-1) != p.charAt(j-2)) && (p.charAt(j-2) != '.')){// p匹配s最后的0个元素 && p的'*'符号前不为'.'符号
dp[i][j] = dp[i][j-2]; // 取决于 去除掉字母+* 结尾元素dp状态
}else {
/**
* dp[i][j-2] 匹配为空的情况
* dp[i][j-1] 匹配一个的情况
* dp[i-1][j] 匹配多个的情况
*/
dp[i][j] = (dp[i][j-2] || dp[i][j-1] || dp[i-1][j]);
}
}
}
}
return dp[s.length()][p.length()];
}
}
典型的动态规划题目,定义一个二维数组,代表的含义为:字符串p的前j个匹配字符串s的前i个是否匹配
这里的难点就是找到状态转移方程,首先分析简单的,当p的第j个字符等于s的第i个时,就有dp[i][j] == dp[i-1][j-1],即dp[i][j]是否匹配取决于利用p的前j-1来匹配s的前i-1个是否匹配成功,当然,当p的第j个等于’.‘也是可以的,然后就是当p的第j个等于’‘时的情况,此时就有三种匹配结果:
1.p的第j-1字符不等于s的第i个字符 此时取决于dp[i][j-2]
2.p的第j-1字符等于恰好等于s的第i个字符一次(即s字符串末尾只有一个字符等于p的第j-1个字符) 此时取决于dp[i][j-1]
3.p的第i-1字符等于多次s的第i个字符(即s字符串末尾位置连续多个字符都等于p的第j-1个字符) 此时取决于dp[i-1][j]
只要满足上面3个条件之一就是匹配成功的,即状态转移方程为:dp[i][j] = (dp[i][j-2] || dp[i][j-1] || dp[i-1][j]);
细节分析:
对于以上的第1个条件,是不能为’.‘和’‘的组合,因为虽然字母不等于符号’.’,但是却能匹配所有的字母。