題目:
給你一個字符串 s
和一個字符規律 p
,請你來實現一個支持 '.'
和 '*'
的正則表達式匹配。
'.' 匹配任意單個字符 '*' 匹配零個或多個前面的那一個元素
所謂匹配,是要涵蓋 整個 字符串 s
的,而不是部分字符串。
說明:
s
可能爲空,且只包含從a-z
的小寫字母。p
可能爲空,且只包含從a-z
的小寫字母,以及字符.
和*
。
示例 1:
輸入: s = "aa" p = "a" 輸出: false 解釋: "a" 無法匹配 "aa" 整個字符串。
示例 2:
輸入: s = "aa" p = "a*" 輸出: true 解釋: 因爲 '*' 代表可以匹配零個或多個前面的那一個元素, 在這裏前面的元素就是 'a'。因此,字符串 "aa" 可被視爲 'a' 重複了一次。
示例 3:
輸入: s = "ab" p = ".*" 輸出: true 解釋: ".*" 表示可匹配零個或多個('*')任意字符('.')。
示例 4:
輸入: s = "aab" p = "c*a*b" 輸出: true 解釋: 因爲 '*' 表示零個或多個,這裏 'c' 爲 0 個, 'a' 被重複一次。因此可以匹配字符串 "aab"。
示例 5:
輸入: s = "mississippi" p = "mis*is*p*." 輸出: false
解題:
public class Solution {
public bool IsMatch(string s, string p)
{
// 先對p做一個格式化,形成等價的p,格式化過程中做以下處理:
// 1、遇到"c*c*"這樣的情形,忽略後一個c*
// 2、遇到"c*c"這樣的的情形將後面的c前移,形成"cc*"
StringBuilder builder = new StringBuilder(), builderTmp = new StringBuilder();
char c = '\0';
for (int i = 0; i < p.Length;)
{
// 判斷下一個字符字符,上述的特殊情況,第二個字符都是*,如果下一個字符會越界,則也不會出現特殊情況
if (i + 1 == p.Length || p[i + 1] != '*')
{
builder.Append(p[i]);
++i;
continue;
}
// 如果是"*",則要判斷是否是上述情況中的一種
c = p[i];
// c*模式
if (i + 2 == p.Length)
{
// c*是最後一個了,不用再管
builder.Append(c); // i
builder.Append('*'); // i+1
i += 2;
break;
}
for (i = i + 2; i + 1 < p.Length;)
{
if (p[i+1] == '*')
{
// 連續的c*
if (c == '.' || p[i] == c)
{
// .*後跟着其他的c*或者連續相同的c*,忽略c*
i += 2;
continue;
}
if (p[i] == '.')
{
// c*後跟着.*,將c改爲.,將builderTmp清空,並將c置爲.
builderTmp.Clear();
c = '.';
i += 2;
continue;
}
// 如果是兩對都沒有.*,那麼將前一對加到builderTmp中,然後c記爲後一對
builderTmp.Append(c);
builderTmp.Append('*');
c = p[i];
i += 2;
// continue;
}
else
{
// 不是連續的c*,那麼先將builderTmp中的內容append到builder中
if (builder.Length > 0)
{
builder.Append(builderTmp.ToString());
builderTmp.Clear();
}
if (p[i] == c)
{
// c*c的模式,將c添加到builder中
builder.Append(c);
++i;
continue;
}
// 其他情況就跳出當前循環,循環外會將當前的c*添加到builder中
break;
}
}
// 如果builderTmp中有內容,則先添加builderTmp,再添加c*
if (builder.Length > 0)
{
builder.Append(builderTmp.ToString());
builderTmp.Clear();
}
// 把c*添加到字符串
builder.Append(c);
builder.Append('*');
// continue;
}
// p格式化完成後,開始匹配
return this.IsMatch(s, builder.ToString(), 0, false, '\0');
}
private bool IsMatch(string s, string p, int begin, bool canSkip, char skipChar)
{
if (begin == s.Length && p.Length == 0)
{
// s和p都是末尾了
return true;
}
if (p.Length == 0)
{
// p沒有了,那就要看是否能跳過頭部以及skipChar的情況
if (canSkip)
{
if (skipChar == '.')
{
// 可以跳過任意字符,直接返回true
return true;
}
for (; begin < s.Length; ++begin)
{
if (s[begin] != skipChar)
{
return false;
}
}
return true;
}
else
{
return false;
}
}
int[] skipDistance = new int[26];
// 找到maxSkip的值
int maxSkip = p.IndexOf('*') - 1;
if (maxSkip < 0)
{
maxSkip = p.Length;
}
if (canSkip && skipChar == '.')
{
// 如果可以跳過頭部並且是任意字符的跳過,則採用sunday算法加速比較,最大跳躍只能到*爲止
int dotIndex = -1;
for (int i = 0; i < p.Length && p[i] != '*'; ++i)
{
if (i + 1 < p.Length && p[i + 1] == '*')
{
// 如果下一個是*,則跳出
break;
}
if (p[i] == '.')
{
dotIndex = i;
continue;
}
skipDistance[p[i] - 'a'] = maxSkip - i;
}
for (int i = 0; i < skipDistance.Length; ++i)
{
if (skipDistance[i] > maxSkip - dotIndex || skipDistance[i] == 0)
{
skipDistance[i] = maxSkip - dotIndex;
}
}
}
int pIndex = 0;
while (begin < s.Length)
{
// 先判斷下一個是否是*
if (pIndex + 1 < p.Length && p[pIndex + 1] == '*')
{
// 遇到c*,進行遞歸
if (this.IsMatch(s, p.Substring(pIndex + 2), begin, true, p[pIndex]))
{
return true;
}
}
else if (pIndex < p.Length && (s[begin] == p[pIndex] || p[pIndex] == '.'))
{
// 相同或者p爲".",過
++begin;
++pIndex;
continue;
}
// 如果遞歸沒有匹配上,或者當前字符不匹配,或者p已經結束了,判斷是否能夠跳過
if (!canSkip)
{
// 不能跳過字符,直接返回false
return false;
}
// 如果能夠跳過,則看是否能夠跳過任意字符
if (skipChar == '.')
{
// 按照sunday算法跳躍,p重置爲0
if (begin - pIndex + maxSkip >= s.Length || begin - pIndex + skipDistance[s[begin - pIndex + maxSkip] - 'a'] >= s.Length)
{
// 跳過後越界,直接返回false
return false;
}
begin += skipDistance[s[begin - pIndex + maxSkip] - 'a'] - pIndex;
pIndex = 0;
continue;
}
// 如果只能跳過特定字符,那麼就判斷s和skipChar是否一致,一致就跳一個字符後後重新從頭匹配
if (s[begin - pIndex] == skipChar)
{
begin = begin - pIndex + 1;
pIndex = 0;
continue;
}
// 能跳過的特定字符與s不匹配,則返回false
return false;
}
// 最後判斷一下begin和pIndex的情況
if (begin == s.Length && pIndex == p.Length)
{
// 都正好匹配結束,返回true
return true;
}
else if (begin < s.Length)
{
// 如果s沒有走完,那就是沒有匹配到最後,返回false
return false;
}
else if (pIndex < p.Length - 1)
{
// p沒有走完,看看其後面的偶數個位置是否都是*,只要有一個不是,那就返回false
if ((p.Length - pIndex) % 2 == 0)
{
// 一定是偶數個
for (pIndex += 1; pIndex < p.Length; pIndex += 2)
{
if (p[pIndex] != '*')
{
return false;
}
}
return true;
}
else
{
return false;
}
}
else
{
// 其他情況
return false;
}
}
}