给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。
1.‘.’ 匹配任意单个字符
2.‘.’ 匹配任意单个字符
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。
输入:s = "aa", p = "a"
输出:false
解释:"a" 无法匹配 "aa" 整个字符串。
输入:s = "aa", p = "a*"
输出:true
解释:因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。
输入:s = "ab", p = ".*"
输出:true
解释:".*" 表示可匹配零个或多个('*')任意字符('.')。
public class Solution {
public bool IsMatch(string s, string p)
{
List<int> M1 = new List<int>() { -1 };
while (p.Length > 0 && M1.Count > 0)
{
List<int> M2 = new List<int>() { };
int pStarIndex = p.IndexOf("*");
foreach (int m1 in M1)
{
string sRight = s.Substring(m1 + 1);
if (pStarIndex == -1)
{
var result = IsMatchNoStar(sRight, p);
//无星说明是p的最后一组,若匹配成功则s与p匹配,直接返回true;否则直接continue
if (result) return true;
continue;
}
//有星,需要 找到 p[0,starIndex] 与 s 的匹配点
//先判断p[0,starIndex-2]的字符与s的一一对应
int mNow = -1;
bool isMatchBeforeStar = true;
for (int i = 0; i < pStarIndex - 1; i++)
{
if (i >= sRight.Length)
{
isMatchBeforeStar = false; break;
}
mNow = i;
if (!IsMatchChar(sRight[i], p[i]))
{
isMatchBeforeStar = false; break;
}
}
if (!isMatchBeforeStar) continue;
//因为*可以表示0个,所以先将mNow添加到 匹配点集
M2.Add(mNow + (m1 + 1));
mNow++;
//再看p[startIndex-1] 与 s的匹配点,找出所有的匹配点
while (mNow < sRight.Length && IsMatchChar(sRight[mNow], p[pStarIndex - 1]))
{
M2.Add(mNow + (m1 + 1));
mNow++;
}
}
//将startIndex以及之前的串给舍弃,留下startIndex+1以及之后的串
p = p.Substring(pStarIndex + 1);
//更新M1
M1 = M2;
}
return M1.Contains(s.Length - 1);
}
public bool IsMatchNoStar(string s, string p)
{
if (s.Length != p.Length)
{
return false;
}
for (int i = 0; i < s.Length; i++)
{
if (!IsMatchChar(s[i], p[i]))
{
return false;
}
}
return true;
}
public bool IsMatchChar(char sc, char pc)
{
return ((pc == '.') || (sc == pc));
}
}
可以牺牲部分可读性 来提高效率, 优化后的代码:
public class Solution {
public bool IsMatch(string s, string p)
{
List<int> M1 = new List<int>() { -1 };
List<int> M2 = new List<int>() { };
int pStart = 0;
while (p.Length - pStart > 0 && M1.Count > 0)
{
int pStarIndex = p.IndexOf("*", pStart);
foreach (int m1 in M1)
{
int sStart = m1 + 1;
int sRightLen = s.Length - sStart;
if (pStarIndex == -1)
{
//IsMatchNoStar
var result = true;
if (s.Length - sStart != p.Length - pStart) result = false;
else
{
for (int i = 0; i < s.Length - sStart; i++)
{
if (!((s[sStart + i] == p[pStart + i]) || (p[pStart + i] == '.')))
{
result = false;
break;
}
}
}
if (result) return true;
continue;
}
int mNow = -1;
bool isMatchBeforeStar = true;
int pLenBeforeStar = pStarIndex - pStart - 1;
for (int i = 0; i < pLenBeforeStar; i++)
{
if (i >= sRightLen)
{
isMatchBeforeStar = false; break;
}
mNow = i;
if (!((s[sStart + i] == p[pStart + i]) || (p[pStart + i] == '.')))
{
isMatchBeforeStar = false; break;
}
}
if (!isMatchBeforeStar) continue;
M2.Add(sStart + mNow++);
int pCharIndexBeforeStar = pStarIndex - 1;
while (mNow < sRightLen && ((s[sStart + mNow] == p[pCharIndexBeforeStar]) || (p[pCharIndexBeforeStar] == '.')))
M2.Add(sStart + mNow++);
}
pStart = pStarIndex + 1;
var tempMs = M1; M1 = M2; M2 = tempMs;
M2.Clear();
}
return M1.Contains(s.Length - 1);
}
}
回溯法解体的思路与分段匹配法类似,但使用递归后,只需要非常少的代码量。
以p为主串,从左向右去匹配s,匹配成功的部分都去掉,也就是说 若最终s与p都变成了空串,则匹配成功。
代码结构也很简单,首先是出口,然后是递归分支。
出口EXIT:p变成空串时,若s也变成了空串,则匹配成功,否则匹配失败。
分支A:p[1]为星号,直接去掉p的前两位,并递归。如 s=“b”,p=“a*b”.
分支B:p[1]为星号时,若s第一位与p第一位匹配,去掉s第一位 , 并递归,如 “s=aab”,p=“ab"。否则匹配失败,如 s=“bba”,p="ab”.
分支C:p[1]不为星号时,若s与p第一位匹配成功, 则都去掉第一位,并递归,如 s=“aab”,p=“aab*”. 否则匹配失败,如 s=“bab”, p=“aab*” .
其中,当p[1]为星号时,分支A与分支B是【或】的关系,只要有一条成功,则匹配成功; 当p[1]不为星号时,就走C。
public class Solution {
public bool IsMatch(string s, string p)
{
//出口,EXIT
if (string.IsNullOrEmpty(p)) return string.IsNullOrEmpty(s); //Exit
bool first_match = (
!string.IsNullOrEmpty(s) &&
(p[0] == s[0] || p[0] == '.')
);
if (p.Length >= 2 && p[1] == '*')
return (IsMatch(s, p.Substring(2)) || // A
(first_match && IsMatch(s.Substring(1), p))); //B
else
return first_match && IsMatch(s.Substring(1), p.Substring(1)); // C
}
}
可以使用下标指针来代替SubString,从而明显的提高代码效率。 优化后的代码:
public class Solution {
public bool IsMatch(string s, string p) { return IsMatch1(s, 0, p, 0); }
public bool IsMatch1(string s, int sStart, string p, int pStart)
{
if (pStart == p.Length) return sStart == s.Length; //Exit
bool first_match = (
sStart < s.Length &&
(p[pStart] == s[sStart] || p[pStart] == '.')
);
if (p.Length - pStart >= 2 && p[pStart + 1] == '*')
return (IsMatch1(s, sStart, p, pStart + 2) || // A
(first_match && IsMatch1(s, sStart + 1, p, pStart))); //B
else
return first_match && IsMatch1(s, sStart + 1, p, pStart + 1); // C
}
}
public class Solution {
public bool IsMatch(string s, string p)
{
bool[,] dp = new bool[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--)
{
bool first_match = (i < s.Length && (p[j] == s[i] || p[j] == '.'));
if (j + 1 < p.Length && p[j + 1] == '*')
{
dp[i, j] = dp[i, j + 2] //A
|| first_match && dp[i + 1, j]; //B
}
else
{
dp[i, j] = first_match && dp[i + 1, j + 1]; // C
}
}
}
return dp[0, 0];
}
}