给定一个字符串 s 和一个字符规律 p,实现一个支持 '.' 和 '*' 的正则表达式匹配。其中,'.' 匹配任意单个字符,而'*' 匹配零个或多个前面的那一个元素。所谓匹配,是要涵盖整个字符串s的,而不是部分字符串。
输入:s = "ab" p = ".*c"
输出:false
https://leetcode-cn.com/problems/regular-expression-matching/
一、递归法
使用双指针i、j的方式对两个字符串s、p进行移位匹配,当双指针分别指向两个字符串的末尾,即二者的大小分别为字符串长度时为匹配成功。否则当模式串已经指针移位结束,而主串的指针还未为主串长度时即匹配失败。具体的匹配的情况可以分成两种:
1、当p[j+1]!='*'时,这时仅需要考虑p[i]和s[j]是否相等的情况,如果相等那么就直接移动到下一个位置,即
主串指针+1,模式串+1
否则因为不匹配就直接返回false。
2、当p[j+1]=='*'且当前主串字符和模式串字符匹配时,那么分为多种情况:
匹配个数为0时,主串指针+0,模式串指针+2,如'a'和'c*'
匹配个数为1时,主串指针+1,模式串指针+2,如'a'和'a*'
匹配个数为2时,主指针+2,模式串指针+2,如'aa'和'a*'
匹配个数为n时,主指针+n,模式串指针+2
从上述情况当中可以看到要实现匹配个数为n时指针的移动是比较困难的,因此不移动模式串指针,而是每次匹配时移动主指针+1,这样就可以从匹配n此变为匹配0次。当然,要怎么判断何时匹配0次?这就需要每次在移动主指针之前先判断匹配个数是否已经是零次了。如果为0,那么就不用再移动主指针,而是将模式指针+2即可,简化为:
主串指针+0,模式串指针+2
若以上匹配错误则执行以下
主串指针+1,模式串指针
根据上面情况如此循环就可以抵达匹配0次的情况。
代码:
bool getMatchResult(int i, int j, const string& s, const string& p)
{
if(j == p.length()) // 这里已经判断j是否越界,因此j + 1可以不用判断
{
return i == s.length(); // 当二者都相等于原字符串长度时则匹配正确
}
if (p[j + 1] == '*')
{
// 为防止后一个getMatch越界需要判断i的大小
// 前一个getMatch的作用是i每次移位之前预先判断p是否已经走完这一次的'*'
if (i != s.length() && (p[j] == s[i] || p[j] == '.'))
return getMatchResult(i, j + 2, s, p) || getMatchResult(i + 1, j, s, p);
else
return getMatchResult(i, j + 2, s, p);
}
else
{
if (i != s.length() && (p[j] == s[i] || p[j] == '.'))
return getMatchResult(i + 1, j + 1, s, p);
else
return false;
}
}
bool isMatch(string s, string p)
{
if (s.empty())
return true;
return getMatchResult(0, 0, s, p);
}
二、动态规划
动态规划的解法在思想上与递归方法一致,但在具体的实现上有所不同。首先需要声明m+1乘n+1大小的二维数组dp,因为在起始的时候dp[0][0]表示的意思是0和0长度的字符串是匹配的。如果起始的dp[0][0]表示的是两个字符串的第一个字符位置,那么就会不知道第一个dp[0][0]到底是true还是false,需要分情况进行讨论,而这已经在后续的循环中已经讨论了。另外,为了判定和递归法里一样的问题,即何时匹配为0,这就需要进行两次判断,第一次判断已经到0次匹配,第二次判断还是多次匹配,取二者的真值即可。
bool isMatch(string s, string p)
{
const int m = s.length(), n = p.length();
vector<vector<int>> dp(m + 1, vector<int>(n + 1));
dp[0][0] = true;
for (int i = 0; i <= m; ++i)
{
for (int j = 1; j <= n; ++j)
{
if (p[j - 1] == '*')
{
if (i > 0 && (s[i - 1] == p[j - 2] || p[j - 2] == '.')) // 不用担心越界问题,因为*前必要字符
{
// |= 表示取二者的真值
dp[i][j] |= dp[i][j - 2];
dp[i][j] |= dp[i - 1][j];
}
else
{
dp[i][j] |= dp[i][j - 2];
}
}
else
{
if (i > 0) // i=0时即不匹配,不用处理
{
if (s[i - 1] == p[j - 1] || p[j - 1] == '.')
{
dp[i][j] |= dp[i - 1][j - 1];
}
else
{
dp[i - 1][j - 1] = false;
}
}
}
}
}
return dp[m][n];
}
进行代码优化:
bool isMatch(string s, string p)
{
const int m = s.length(), n = p.length();
vector<vector<int>> dp(m + 1, vector<int>(n + 1));
dp[0][0] = true;
auto matches = [&](int i, int j) {
// if (i == 0) return false;
return i > 0 && (s[i - 1] == p[j - 1] || p[j - 1] == '.');
};
for (int i = 0; i <= m; ++i)
{
for (int j = 1; j <= n; ++j)
{
if (p[j - 1] == '*')
{
dp[i][j] |= dp[i][j - 2];
if (matches(i, j - 1)) // 不用担心越界问题,因为*前必要字符
{
// |= 表示取二者的真值
dp[i][j] |= dp[i - 1][j];
}
}
else
{
if (matches(i, j))
{
dp[i][j] = dp[i - 1][j - 1];
}
}
}
}
return dp[m][n];
}