1. 程式人生 > >LeetCode 647. Palindromic Substrings的三種解法

LeetCode 647. Palindromic Substrings的三種解法

轉載地址

https://www.cnblogs.com/AlvinZH/p/8527668.html#_label5


題目詳情

給定一個字串,你的任務是計算這個字串中有多少個迴文子串。

具有不同開始位置或結束位置的子串,即使是由相同的字元組成,也會被計為是不同的子串。

示例 1:

輸入: "abc"
輸出: 3
解釋: 三個迴文子串: "a", "b", "c".

示例 2:

輸入: "aaa"
輸出: 6
說明: 6個迴文子串: "a", "a", "a", "aa", "aa", "aaa".

注意:

  1. 輸入的字串長度不會超過1000。

題目分析

一個小問題,子串(Substring)、子陣列(Subarray)和子序列(Subsequence)的區別:子串和子陣列是等同的,特點是連續的,比如[1,2,3]的子串有(1), (2), (3), (1,2), (2,3), (1,2,3)。而子序列不一定相鄰,但相對順序一致,比如(1,3)是[1,2,3]的一個子序列。

方法有很多種,簡單講一些。


方法一: DP

一開始定義DP[i][j]為i、j之間的迴文子串數,很是麻煩,還需要另外的陣列記錄子串[i, j]是否是迴文的。其實沒有必要,直接將DP[i][j]定義成子串[i, j]是否是迴文串。外迴圈 i從 n−1 往 0 遍歷,內迴圈 j 從 i 往 n−1 遍歷,若s[i]==s[j]:

若i==j,則dp[i][j]=true;
若i和j是相鄰的,則dp[i][j]=true;
若i和j中間只有一個字元,則dp[i][j]=true;
否則,檢查dp[i+1][j-1]是否為true,若為true,那麼dp[i][j]就是true。
前三條可以合併,即 j−i≤2。求得dp[i][j]真值後,如果其為true,最終結果res++。

時間複雜度:O(n^2)。

方法一參考程式碼:

class Solution {
public:
    int countSubstrings(string s) {
        int len = s.size(), res =
0; vector<vector<bool>> dp(len, vector<bool>(len, false)); for (int i = len - 1; i >= 0; --i) { for (int j = i; j < len; ++j) { dp[i][j] = (s[i] == s[j]) && (j - i <= 2 || dp[i + 1][j - 1]); if (dp[i][j]) ++res; } } return res; } };

方法二:迴文中心法

本題可以不用DP,而是採用一種巧妙的方法:迴文中心法。什麼意思呢?考慮不同的迴文中心,往兩邊擴散,求得迴文數。需要考慮兩種情況:如果是奇數長度迴文串,了麼迴文中心為最中間的一個字元;如果是偶數長度迴文串,這回文中心為最中間的兩個字元。

每個迴文子串只有一個迴文中心,所以這種方法不會重複計算,也不會漏算。

時間複雜度:O(n^2)。

方法二參考程式碼:

class Solution {
public:
    int countSubstrings(string s) {
        int len = s.size(), res = 0;
        for (int i = 0; i < len; ++i) {
            int mid1 = i, mid2 = i;//奇數
            while (mid1 >= 0 && mid2 < len && s[mid1] == s[mid2]) {
                --mid1; ++mid2; ++res;
            }
            
            mid1 = i, mid2 = i+1;//偶數
            while (mid1 >= 0 && mid2 < len && s[mid1] == s[mid2]) {
                --mid1; ++mid2; ++res;
            }
        }
        return res;
    }
};

方法三:“馬拉車”演算法

神奇的演算法,先馬一下,學會再寫上。聽說時間複雜度是 O(n)。

好了,學到了,請參考:什麼是馬拉車演算法?

利用馬拉車演算法,可以得到所有情況下的最大半徑,以s[i]為中心,RL[i]為半徑的迴文串中含有的字迴文串數目是 RL[i]/2 個。

方法三參考程式碼:

class Solution {
public:
    int countSubstrings(string s) {
        //預處理
        string t = "#";
        for (int i = 0; i < s.size(); ++i) {
            t += s[i];
            t += "#";
        }

        vector<int> RL(t.size(), 0);
        int MaxRight = 0, pos = 0;
        int res = 0;
        for (int i = 0; i < t.size(); ++i) {
            RL[i] = MaxRight > i ? min(RL[2 * pos - i], MaxRight - i) : 1;

            while (i-RL[i] >=0 && i+RL[i] < t.size() && t[i + RL[i]] == t[i - RL[i]])//擴充套件,注意邊界
                ++RL[i];
            //更新最右端及其中心
            if (MaxRight < i + RL[i] -1) {
                MaxRight = i + RL[i] -1;
                pos = i;
            }
            
            res += RL[i]/2;
        }
        return res;
    }
};