Task18——正則表達式匹配(待更新)

題目:

給你一個字符串 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;
        }
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章