1. 程式人生 > 實用技巧 >求迴文串方法小結

求迴文串方法小結

記錄一下在一個字串中求迴文串的三種常用的方法:

練手題目:https://leetcode-cn.com/problems/palindromic-substrings/

方法一:這個也是我們一般人最容易想到的,就是直接列舉左右端點,然後去判斷兩個端點內的字串是不是迴文串就行了,時間複雜度O(n^3)

class Solution {
public:
    string S;
    bool check(int left,int right){
        while (left < right){
            if (S[left] == S[right]){
                left++;
                right--;
            }else return false;
        }
        return true;
    }
    int countSubstrings(string s) {
        S = s;
        int n = s.size(),ans = 0;
        for (int i = 0; i < n; i++){
            for (int j = i; j < n; j++){
                if (check(i,j)) ans++;//定左右端點並check一下
            }
        }
        return ans;
    }
};

方法二(1):只要是迴文串,那麼它肯定有迴文中心。這樣我們第二個思路也就出來了,去列舉迴文中心,然後不斷往兩邊去拓展,具體步驟結合題目看程式碼,時間複雜度O(n^2)

class Solution {
public:
    int countSubstrings(string s) {
        int n = s.size(),ans = 0;
        for (int i = 0; i < 2*n-1; i++){//迴文中心可能為字元,也可能為空(迴文長度為偶數的時候),這樣對於長度為n的字串,總共就有2n-1個地方需要去列舉
            int left = i/2,right = i/2;
            if (i % 2) right++; //可以在紙上畫一畫就明白了
            while(s[left] == s[right]){
                ans++;
                left--;
                right++;
                if (left < 0 || right >= n) break;
            }
        }
        return ans;
    }
};

方法二(2):受方法一的啟發,我們定好一個右端點j後,不斷在前面選左端點i,如果s[i] != s[j],那肯定不行,否則我們就要判斷s[i+1] ~ s[j-1]是不是迴文串。我們突然靈光一現,好像一個記憶化。我們開一個二維陣列dp[i] [j]表示從下標i到下標j 這樣的字串是否是迴文串,轉移方程式為:

dp[i][j] =  dp[i +1][j - 1] && (s[i] == s[j])

這樣的時間複雜度也是O(n^2),但與方法二(1)相比還多了個數組,所以總體沒有方法二(1)優的

class Solution {
public:
    int countSubstrings(string s) {
        int n = s.size();
        vector<vector<bool>> dp(n,vector<bool>(n,false));
        int ans = 0;
        for (int i = 0; i < n; i++) dp[i][i] = true,ans++;
        for (int i = 0; i < n; i++){
            for (int j = 0; j < i; j++){
                if (s[i] != s[j]) continue;
                else{
                    if (dp[j+1][i-1] || j+1 > i-1){
                        ans++;
                        dp[j][i] = true;
                    }
                }
            }
        }
        return ans;
    }
};

方法三:馬拉車演算法,沒有接觸過的可以去看練習題目的官方題解,這裡記錄一些要點:

1.馬拉車演算法要求我們維護當前最大的迴文的右端點 rmax以及這個迴文右端點對應的迴文中心mid。

2.在拓展後的字串左邊加‘$’,右邊加‘!’的目的是:為了省事,在拓展的時候防止下標越界!

3.對於每一個f[i] 來說(f[i]就是以i為迴文中心的最大回文半徑),如果i在rmax裡面,那麼我們就可以利用先前已經知道答案:f[i] = min(rMax - i + 1, f[2 * iMax - i]),否則先前知道的答案對i沒什麼用:f[i] = 1.

4.對於拓展後的字串對應的f[i] 來說,它所對應的原迴文串的長度是 f[i] - 1(可以用數學關係推)

時間複雜度為O(n),可以說是很快了

class Solution {
public:
    int countSubstrings(string s) {
        int n = s.size();
        string S = "$";
        for (int i = 0; i < n; i++){
            S.push_back('#');
            S.push_back(s[i]);
        }
        S += "#";
        S += "!";
        vector<int> f(S.size());
        int ans = 0,mid = 0,rmax = 0;
        for (int i = 1; i < S.size(); i++){
            if (i <= rmax){
                f[i] = min(f[2*mid-i], rmax-i+1);
            }else f[i] = 1;

            while(S[i-f[i]] == S[i+f[i]]) f[i]++;
            if (i+f[i]-1 > rmax){
                rmax = i+f[i]-1;
                mid = i;
            }
            ans += f[i]/2;
        }
        return ans;
    }
};