天天看點

leetcode 刷題 回文子串(647)

leetcode 刷題 回文子串(647)

其他類似的求最長回文子串,也是同樣的思路

int dp[1001][1001];//dp[i][j]表示從i到j的子字元串是否為回文,若是為1
    int countSubstrings(string s) {
        int n=s.size();
        int ans=0;
        for(int i=0;i<n;i++)//預處理子字元串長度為1,為2的
        {
            dp[i][i]=1;
            ans++;
            if(i<n-1)
            {
                if(s[i]==s[i+1])
                {
                        dp[i][i+1]=1;
                        ans++;
                }
            }
        }
        for(int L=3;L<=n;L++)//從字元串長度為3開始枚舉
        {
            for(int i=0;i+L-1<n;i++)//枚舉左端點
            {
                int  j=i+L-1;//計算右端點
                if(s[i]==s[j]&&dp[i+1][j-1]==1)//狀态轉移方程
                {
                        dp[i][j]=1;
                        ans++;
                }

            }
        }
        return ans;
        

    }
           

還有Manacher 算法

在所有的相鄰字元中間插入 #,比如 abaa 會被處理成 #a#b#a#a#,這樣可以保證所有找到的回文串都是奇數長度的。

用 f(i)來表示以 s的第 i位為回文中心,可以拓展出的最大回文半徑,那麼 f(i) - 1就是以 i 為中心的最大回文串長度 。

假設我們已經計算好了 [1, i - 1]區間内所有點的 f(即[1,i−1] 這些點作為回文中心時候的最大半徑), 那麼我們也就知道了 [1, i - 1]拓展出的回文達到最大半徑時的回文右端點。例如 i=4 的時候 f(i) = 5,說明以第 4 個元素為回文中心,最大能拓展到的回文半徑是 5,此時右端點為 4 + 5 - 1 = 8。是以當我們知道一個 i對應的 f(i)的時候,我們就可以很容易得到它的右端點為 i + f(i) - 1。

Manacher 算法如何通過已經計算出的狀态來更新 f(i) 呢?Manacher 算法要求我們維護「目前最大的回文的右端點 r_m 」以及這個回文右端點對應的回文中心 i_m。我們需要順序周遊 s,假設目前周遊的下标為 i。我們知道在求解 f(i)之前我們應當已經得到了從 [1, i - 1]所有的 f,并且目前已經有了一個最大回文右端點 r_m以及它對應的回文中心 i_m。

初始化 f(i)

如果 i<r_m ,說明 i被包含在目前最大回文子串内,假設 j是 i關于這個最大回文的回文中心 i_m的對稱位置(即 j + i = 2×i_m),我們可以得到 f(i)至少等于 min{f(j), r_m - i + 1}。這裡将 f(j)和 r_m - i + 1取小,是先要保證這個回文串在目前最大回文串内。(思考:為什麼 f(j)f(j) 有可能大于 r_m - i + 1?)

如果 i > r_m,那就先初始化 f(i) = 1。

中心拓展

做完初始化之後,我們可以保證此時的 s[i + f(i) - 1] = s[i - f(i) + 1],要繼續拓展這個區間,我們就要繼續判斷 s[i + f(i)]和 s[i - f(i)]是否相等,如果相等将 f(i) 自增;這樣循環直到 s[i + f(i)] 不等于s[i - f(i)],以此類推。我們可以看出循環每次結束時都能保證 s[i + f(i) - 1] = s[i - f(i) + 1],而循環繼續(即可拓展的條件)一定是 s[i + f(i)] = s[i - f(i)]。 這個時候我們需要注意的是不能讓下标越界,有一個很簡單的辦法,就是在開頭加一個 $,并在結尾加一個 !,這樣開頭和結尾的兩個字元一定不相等,循環就可以在這裡終止。

這樣我們可以得到 ss所有點為中心的最大回文半徑,也就能夠得到 S 中所有可能的回文中心的的最大回文半徑,把它們累加就可以得到答案。

 代碼如下:

class Solution {
public:
    int countSubstrings(string s) {
        int n = s.size();
        string t = "$#";
        for (const char &c: s) {
            t += c;
            t += '#';
        }
        n = t.size();
        t += '!';

        auto f = vector <int> (n);
        int iMax = 0, rMax = 0, ans = 0;
        for (int i = 1; i < n; ++i) {
            // 初始化 f[i]
            f[i] = (i <= rMax) ? min(rMax - i + 1, f[2 * iMax - i]) : 1;
            // 中心拓展
            while (t[i + f[i]] == t[i - f[i]]) ++f[i];
            // 動态維護 iMax 和 rMax
            if (i + f[i] - 1 > rMax) {
                iMax = i;
                rMax = i + f[i] - 1;
            }
            // 統計答案, 目前貢獻為 (f[i] - 1) / 2 上取整
            ans += (f[i] / 2);
        }

        return ans;
    }
};

作者:LeetCode-Solution
連結:https://leetcode-cn.com/problems/palindromic-substrings/solution/hui-wen-zi-chuan-by-leetcode-solution/
來源:力扣(LeetCode)
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。