一、题目
本文主要是记录一下写这道题时的理解过程,主要是参考题解后的思考,可能有些地方会不太准确。
思路分析
先思考一下普通的两个字符串的匹配:两个指针i,j分别在字符串s和字符串p上移动,如果s[i] == p[j],就移动指针,否则匹配失败;也就是说,两个字符的匹配结果只有两种:相等或不相等。 而这道题中新引入了两个符号'.'和'*':
'.'
:可以当做任意单个字符,也就是说在p中遇到'.',当成一个字符处理'*'
: 匹配零个/多个前面的那一个元素,假设前面的元素为a,那么a*这个组合可以当做n个a(n>=0)的组合使用
对于这种每次的选择的都有多种的情况,可以采取动态规划穷举的方式:
- 定义:
dp[i][j]
代表s中长度为i的字符串是否能匹配p中长度为j的字符串 - 初始条件:
dp[0][0]
代表s为空串且p为空串,可以匹配,为true - 状态转移:对于每个
dp[i][j]
,假设dp[..i][..j]
已经知道:
p.charAt(j)!='*'
:如果p.charAt(j)=s.charAt(i)
,那么当下两个字符匹配成功,dp[i][j]
是否匹配成功取决于dp[..i-1][..j-1]
是否匹配成功,即dp[i][j]==dp[i-1][j-1]
if(p.charAt(j)!='*'){
if(isMatch(s,i,p,j)){
dp[i][j]=dp[i-1][j-1];
}
}
p.charAt(j)=='*'
: 如果前一个字符p.charAt(j-1)和s.charAt(i)相匹配,这个字符可以继续使用,也就是说下标为i和下标为j的两个字符已经匹配成功,可以尝试让该字符继续和s的前一个字符相匹配(dp[i][j]=dp[i-1][j]
),也可以不再使用这个字符(dp[i][j]=dp[i][j-2]
),只要有一种方式成功即可;当然,如果p.charAt(j-1)!=s.charAt(i),可以直接舍弃这个字符组合了(dp[i][j]=dp[i][j-2]
)
if(p.charAt(j - 1) == '*'){
dp[i][j]=dp[i][j-2];
if(isMatch(s,i,p,j-1)){
dp[i][j]=dp[i-1][j]||dp[i][j];
}
}
代码实现
class Solution {
public boolean isMatch(String s, String p) {
int m = s.length();
int n = p.length();
// dp[i][j]表示s中的i个字符是否合p中的j个字符匹配
boolean[][] dp = new boolean[m+1][n+1];
// 空字符串跟空字符串匹配
dp[0][0] = true;
// 注意实际的s p下标从1开始
for(int i=0;i<=m;i++){
for(int j=1;j<=n;j++){
if(p.charAt(j - 1) == '*'){
dp[i][j]=dp[i][j-2];
if(isMatch(s,i,p,j-1)){
dp[i][j]=dp[i-1][j]||dp[i][j];
}
}else{
if(isMatch(s,i,p,j)){
dp[i][j]=dp[i-1][j-1];
}
}
}
}
return dp[m][n];
}
// 判断两个字符是否相等
public boolean isMatch(String s, int i, String p, int j){
// s为空串,p不为空串,匹配失败
if(i==0){
return false;
}
// '.'当成一个字符 匹配成功
if(p.charAt(j - 1) == '.'){
return true;
}
return p.charAt(j-1)==s.charAt(i-1);
}
}
-
时间复杂度:两个循环,O(MN)
-
空间复杂度:dp数组 O(MN)