leetcode-10.正则表达式匹配
字符串编辑类问题
题目详情
给你一个字符串 s
和一个字符规律 p
,请你来实现一个支持'.'
和'*'
的正则表达式匹配。
'.'
匹配任意单个字符'*'
匹配零个或多个前面的那一个元素
示例1:
输入:s = "aa", p = "a"
输出:false
解释:"a" 无法匹配 "aa" 整个字符串。
示例2:
输入:s = "aa", p = "a*"
输出:true
解释:因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。
示例3:
输入:s = "ab", p = ".*"
输出:true
解释:".*" 表示可匹配零个或多个('*')任意字符('.')。
思路:
我这道题弄迷糊的一个点:
dp
的下标和p s
的下标不是严格对应的
dp[i][j]
对应的s p
末尾字符是s[i-1] p[j-1]
而不是i j
弄清了这一点之后就可以分情况讨论了
我们可以使用一个二维数组 dp
,其中 dp[i][j]
表示以 i
截止的字符串是否可以被以 j
截止的正则表达式匹配。根据正则表达式的不同情况,即字符、星号,点号,我们可以分情况讨论来更新 dp
数组
大概分的情况:
1.特殊情况(s
和p
是否为null
)
2.一般情况:(由正则p的末尾开始往前分析)
<1>末尾为.
<2>末尾为✳
<3>末尾不为✳
因为<3>的情况比<2>简单,所以代码中if else
的顺序是<1><3><2>
具体的实现方法和细节见代码注释
我的代码:
class Solution
{
public:
bool isMatch(string s, string p)
{
int m = s.size(), n = p.size();
vector<vector<bool>> dp(m + 1, vector<bool>(n + 1, false)); //初始化
/**
这里先看特殊情况: sp为两个null串 √
s!=null p==null ×
s==null p!=null 分情况
*/
dp[0][0] = true; // s p都为null 为true
//s==null p!=null的情况存在ture,所以我们要把这种情况的dp初始化一下
for (int i = 1; i <= n; ++i) //根据正则表达式中的*情况来初始化dp[0]
{
//如果正则表达式末尾是*
if (p[i-1] == '*')
dp[0][i] = dp[0][i-2];
//那么以p[i-1]位置的匹配情况取决于p[i-3]结尾位置
//(因为前面位置可以被*消掉从而匹配空的s)
}
//分析匹配(下面分析的情况s p都不为空)
for (int i = 1; i <= m; ++i)
{
for (int j = 1; j <= n; ++j)
{
//如果正则末尾是.(匹配单个字符)
if (p[j-1] == '.')
//因为.可以匹配任何单个字符,所以dp[i][j]取决于dp[i-1][j-1](看剩余前面的子串是否匹配)
dp[i][j] = dp[i-1][j-1];
//如果末尾不是*(无法重复或抵消前面字符从而影响后面)(老老实实判断)
else if (p[j-1] != '*')
//取决于前面子串的情况 && 末尾字符相同与否
dp[i][j] = dp[i-1][j-1] && p[j-1] == s[i-1];
//(末尾为*) 连p[j-2](*的前一个字符)都无法匹配s[i-1](s末尾)
//&& p[j-2]不为. 那么只能用*把p[j-2]抵消掉了
else if (p[j-2] != s[i-1] && p[j-2] != '.')
dp[i][j] = dp[i][j-2]; //然后取决于p[j-2]前面那一段(对应的dp是[i][j-2])是不是匹配s
else //最复杂的一种情况
// *前重复一次 *前≥2次 *前0次(抵消掉)
dp[i][j] = dp[i][j-1] || dp[i-1][j] || dp[i][j-2];
}
}
return dp[m][n];
}
};
这里单独解释一下最后一个else
dp[i][j-2]和dp[i][j-1]
很容易理解,就是把✳前面的那个字符重复了0-1次
而dp[i-1][j]
可以代表✳重复了≥2次,这是为什么呢
我们不妨分析为什么要重复大于等于两次,假设是a✳ 那么我们是为了让a这个字符不止一次和s串的末尾进行匹配,我们可以理解成a※变成了a✳a 后面这个a和s的末尾匹配对应好了,然后还可以利用a✳这个正则继续匹配s剩余的子串