10.Regular Expression Matching
‘.’ Matches any single character. ‘*’ Matches zero or more of the
preceding element.The matching should cover the entire input string (not partial).
The function prototype should be: bool isMatch(const char *s, const
char *p)Some examples:
isMatch(“aa”,”a”) → false
isMatch(“aa”,”aa”) → true
isMatch(“aaa”,”aa”) → false
isMatch(“aa”, “a*”) → true
isMatch(“aa”,”.*”) → true
isMatch(“ab”, “.*”) → true
这是一道有关正则表达式的题目。其中“.”代表任意一个字符,“*”代表其前面的字符可以出现任意次(包括0次)。
每次从字符串拿出一个字符和模式中的字符去匹配
- 如果两个字符相等或者模式中的字符为“.”,此时这两个字符匹配成功,那么接着匹配后面的字符
- 当模式中的字符为*时处理起来比较困难。
- 如果“* ”之前的字符a和字符串中字符b不匹配(即a,b不相等或a不是”.”),则应该忽略掉“ a*”,接着向后匹配
- 如果“* ”之前的字符a和字符串中字符b匹配(即a,b相等或a是”.”),那么这又有几种情况可以选择:
- b和“a*”匹配一次,然后接着往后匹配
- b和“a*”匹配多次,即接着匹配b的下一个字符和a*
- b不和“a*”匹配,接着将b和a*后面的字符进行匹配
顺着这个思路可以写出一个解法:
public class Solution {
public boolean isMatch(String s, String p) {
return match(s,p,0,0);
}
public boolean match(String s, String p,int i,int j){
if(i==s.length() && j == p.length()) return true;
if(i!=s.length() && j == p.length()) return false;
//当下一个匹配字符是*时
if(j!=p.length()-1 && p.charAt(j+1) == '*'){
//如果当前匹配字符与字符串字符相等
if(i != s.length() && (p.charAt(j) == '.' ||s.charAt(i) == p.charAt(j))){
//还是当前匹配模式,匹配下一个字符
return match(s,p,i+1,j)
//到下一次匹配,即a*只匹配一次
|| match(s,p,i+1,j+2)
//忽略这个*匹配模式,即a*匹配0次,匹配当前字符和下一匹配字符
|| match(s,p,i,j+2);
}else{
//当前字符和匹配字符不等,直接忽略这个匹配模式,匹配下一个
return match(s,p,i,j+2);
}
}
if(i != s.length() && (p.charAt(j) == '.' ||s.charAt(i) == p.charAt(j))){
return match(s,p,i+1,j+1);
}
return false;
}
}
但这种方法极慢,应该是在 return match(s,p,i+1,j)|| match(s,p,i+1,j+2)|| match(s,p,i,j+2);
这个过程中递归层次会过深。
考虑使用动态规划来解决这个问题。
dp[i][j]代表字符串前i个字符和模式前j个字符的匹配结果。
(这个下标的对应关系搞得我很混乱啊啊啊 (ノಠ益ಠ)ノ彡┻━┻)
首先可以根据找出的匹配规律写出如下规则:
1, If p.charAt(j) == s.charAt(i) : dp[i][j] = dp[i-1][j-1];
2, If p.charAt(j) == '.' : dp[i][j] = dp[i-1][j-1];
3, If p.charAt(j) == '*':
1 if p.charAt(j-1) != s.charAt(i) : dp[i][j] = dp[i][j-2]
//in this case, a* only counts as empty
2 if p.charAt(j-1) == s.charAt(i) or p.charAt(j-1) == '.':
dp[i][j] = dp[i-1][j]
//in this case, a* counts as multiple a
or dp[i][j] = dp[i][j-1]
// in this case, a* counts as single a
or dp[i][j] = dp[i][j-2]
// in this case, a* counts as empty
public class Solution {
public boolean isMatch(String s, String p) {
if(s == null || p == null) return false;
boolean[][] dp = new boolean[s.length()+1][p.length()+1];
dp[0][0] = true;
for (int i = 2; i <= p.length(); i++) {
if (p.charAt(i-1) == '*' && dp[0][i-2]) {
dp[0][i] = true;
}
}
for(int i=1;i<=s.length();i++){
for(int j=1;j<=p.length();j++){
if(p.charAt(j-1) == '.'){
dp[i][j] = dp[i-1][j-1];
}
if(p.charAt(j-1) == s.charAt(i-1)){
dp[i][j] = dp[i-1][j-1];
}
if(p.charAt(j-1) == '*'){
if(p.charAt(j-2) != s.charAt(i-1)&& p.charAt(j-2)!='.'){
dp[i][j] = dp[i][j-2];
}else{
dp[i][j] = dp[i-1][j] || dp[i][j-1] || dp[i][j-2];
}
}
}
}
return dp[s.length()][p.length()];
}
}
动规算法明显加快,因为是根据前面的结果计算当前的结果,不会有前面的递归层次深的问题。
虽然思想是一样的,我总是觉得动规的代码写出来不顺,不是那么容易理解。可能因为前一个是自底向上,动规有点自顶向下的感觉。