Given an input string (s
) and a pattern (p
), implement regular expression matching with support for '.'
and '*'
.
'.' Matches any single character. '*' Matches zero or more of the preceding element.
The matching should cover the entire input string (not partial).
Note:
s
could be empty and contains only lowercase lettersa-z
.p
could be empty and contains only lowercase lettersa-z
, and characters like.
or*
.
Example 1:
Input: s = "aa" p = "a" Output: false Explanation: "a" does not match the entire string "aa".
Example 2:
Input: s = "aa" p = "a*" Output: true Explanation: '*' means zero or more of the precedeng element, 'a'. Therefore, by repeating 'a' once, it becomes "aa".
Example 3:
Input: s = "ab" p = ".*" Output: true Explanation: ".*" means "zero or more (*) of any character (.)".
Example 4:
Input: s = "aab" p = "c*a*b" Output: true Explanation: c can be repeated 0 times, a can be repeated 1 time. Therefore it matches "aab".
Example 5:
Input: s = "mississippi" p = "mis*is*p*." Output: false
解题思路:
对于该类问题,我们首先需要想到是否能够使用递归来实现编程。对于该题,“.”代表可以匹配任意一个字符,‘*’可以匹配零个或者多个字符。对于该题,我们不难发现,无论字符串多长,其总是按照这些规则进行匹配。于是,我们可以分为几种情况讨论并利用递归来进行进一步判断
情况1:字符串s为空串,则当p为空串返回true,否则返回false
情况2:字符串s不为空串:
1.判断第一个字符
倘若s[0]与p[0]一致,或者p[0]=='.',由于‘.’可以匹配任意字符,故返回true。
倘若不符合,返回false
2.判断接下来的字符
倘若p[1]=='*',则需要同时考虑两种情况,一种是无视*号,即*号匹配0个字符,所以将p模式截取掉*号作为参数放入递归式中;一种是利用*号匹配并且由于*号可以匹配连续的任意多个字符。所以将截取s字符作为参数放入递归式中
倘若p[1] != '*',则s和p都进行截断操作,并作为参数放入递归式中。
实现代码如下:
class Solution {
public boolean isMatch(String s, String p) {
//如果模式为空
if(p.isEmpty())
return s.isEmpty();
//判断第一个字符是否匹配
boolean first_match = (!s.isEmpty() &&
(p.charAt(0) == s.charAt(0) || p.charAt(0) == '.'));
if(p.length() >= 2 && p.charAt(1) == '*') {
//出现了*号,即需要考虑可以匹配多个符号
return (isMatch(s,p.substring(2)) ||
(first_match && isMatch(s.substring(1),p)));
} else {
return first_match && isMatch(s.substring(1),p.substring(1));
}
}
}
我们不难发现,以上这种写法将会导致出现每次递归都会去重复计算的子问题,即重叠子问题。这时我们便自然而然地想到是否可以利用动态规划思想来解决问题呢?
将上述的代码稍微修改一下,便可以得到自顶向下的动态规划方案
代码如下:
class Solution {
Result[][] memo;//备忘录
public boolean isMatch(String s, String p) {
memo = new Result[s.length()+1][p.length()+1];
return dp(0,0,s,p);
}
public boolean dp(int i,int j,String s,String p) {
if(memo[i][j] != null) {
return memo[i][j] == Result.TRUE;
}
boolean ans;
if(j == p.length()) {
//思考,这里为什么用p的长度来作为判断呢?因为*号可以匹配任意多的字符
ans = i==s.length();
} else {
//模式字符还未用完
boolean first_match = (i < s.length() &&
(p.charAt(j) == s.charAt(i)
||p.charAt(j) == '.'));
if(j+1 < p.length() && p.charAt(j+1) == '*') {
ans = (dp(i,j+2,s,p) ||
first_match && dp(i+1,j,s,p));
} else {
ans = first_match && dp(i+1,j+1,s,p);
}
}
memo[i][j] = ans? Result.TRUE : Result.FALSE;
return ans;
}
}
enum Result {
TRUE,FALSE
}
再将上述代码修改一下,改为自底向上写法如下:
class Solution {
public boolean isMatch(String s, String p) {
boolean[][] dp = new boolean[s.length() + 1][p.length() + 1];
//两个都为空串时匹配
dp[s.length()][p.length()] = true;
for(int i = s.length();i>=0;i--){
for(int j = p.length() - 1;j >= 0;j--) {
boolean first_match = (i < s.length() &&
(p.charAt(j) == s.charAt(i) ||
p.charAt(j) == '.'));
if(j + 1 < p.length() && p.charAt(j + 1) == '*') {
//这里其实利用了短路原则,所以在对j+1的字符进行操作时不会有越界异常抛出
dp[i][j] = dp[i][j + 2] || first_match && dp[i + 1][j];
} else {
dp[i][j] = first_match && dp[i + 1][j + 1];
}
}
}
return dp[0][0];
}
}