最長迴文子串的幾種求法

題目鏈接:最長迴文子串

一、暴力求解O(n3)O(n^3)

\quad直接枚舉兩端點i,ji,j,再判斷字符串s[i,,,j]s[i,,,j]是否是迴文串,一共三重循環。

class Solution {
public:
    string longestPalindrome(string s) {
        int n = s.length();
        pair<int, int> p = {0, 0};  // 記錄迴文串起始位置和長度
        for(int i = 0; i < n; i++)
        {
            if(p.second == n) break;  // 整個字符串都是迴文串,可以直接退出
            for(int j = i; j < n; j++)
            {
                int l = i, r = j;
                while(l <= r && s[l]==s[r])  l++, r--;
                if(l >= r && j-i+1 > p.second) p = {i, j-i+1};
            }
        }
        return s.substr(p.first, p.second);
    }
};

\quad這個暴力的空間複雜度爲O(1)O(1)

二、優化的暴力O(n2)O(n^2)

\quad從每個字符出發,沿左右分別前進,直到左右兩邊字符不同時退出。舉個例子,s="dabacd",從第三個字母b出發,向左和向右走一步都得到a,表明當前aba是迴文串,繼續向左走得到d,向右走得到c,不同,表示dabac不是迴文串,結束循環。可以得到以第三個字母b爲中心的最長迴文串爲aba。上述情況爲最長迴文串長度爲奇數的情況,如果是s"baabd"這種情況,最長迴文串爲baab,長度爲奇數,可以在每兩個字符間插入一個額外字符t="b#a#a#b#c",這樣兩種情況統一。

class Solution {
public:
    string longestPalindrome(string s) {
        string t = "";
        for(int i = 0; i < s.length(); i++)
            t += s[i], t += '#';
        int n = t.length() - 1;
        string res = "";
        for(int i = 0; i < n; i++)
        {
            // 不可能找到比當前更長的最長迴文串,提前break
            if(res.length() > n-i) break;
            string temp = "";
            int l = i, r = i;
            while(l>=0 && r<n && t[l]==t[r]) l--, r++;
            for(int j = l+1; j <= r-1; j++)
                if(t[j]!='#') temp += t[j];
            if(temp.length() > res.length()) res = temp;
        }
        return res;
    }
};

三、動態規劃O(n2)O(n^2)

\quadp[i][j]表示字符串從i到j是否是迴文串,p[i][j]初始化爲false,當且僅當s[i]==s[j]&&j-i<3或者s[i]==s[j]&&p[i+1][j-1]==truep[i][j]=true。因爲判斷p[i][j]的狀態需要知道p[i+1][j]的狀態,所以我們第一重遍歷i需要從大到小進行。同時記錄迴文串起始位置和長度信息以便更新。

class Solution {
public:
    string longestPalindrome(string s) {
        int n = s.length();
        if(n<2) return s;
        bool dp[n][n] = {false};
        pair<int, int> p = {0, 0};  // 記錄迴文串起始位置和長度
        for(int i = n-1; i >= 0; i--)
            for(int j = i; j < n; j++)
            {
                if(s[i]==s[j] && (j-i<3 || dp[i+1][j-1]==true))
                {
                    dp[i][j] = true;
                    if(j-i+1 > p.second) p = {i, j-i+1};
                }
            }
        return s.substr(p.first, p.second);
    }
};

四、manacher算法O(n)O(n)

\quad跟第二種方法類似,只是我們統計以某個字母爲中心的最長迴文串時採取更加高效的措施,這就是“馬拉車”算法。

struct P{
    int l, r, len; // 起點,終點,實際長度
};
class Solution {
public:
    string longestPalindrome(string s) {
        if(s.length() < 2) return s;
        string t;
        for(auto c: s) t += c, t += '#';
        int n = t.length() - 1;
        int d[n] = {0};
        P p = {0, 0, 0}; // {開始位置,長度}
        for(int i = 0, l = 0, r = -1; i < n; i++)
        {
            int k = (i > r) ? 1 : min(d[l + r - i], r - i);
            while(0 <= i - k && i + k < n && t[i - k] == t[i + k]) k++;
            d[i] = k--;
            int start = i - d[i] + 1;
            int len = t[start] == '#' ? (d[i] * 2 - 2) / 2 : (d[i] * 2) / 2;
            if(len > p.len) p = {start, i + d[i] - 1, len};
            if(i + k > r) l = i - k, r = i + k;
        }
        string res = "";
        for(int i = p.l; i <= p.r; i++) if(t[i] != '#') res += t[i];
        return res;
    }
};

在這裏插入圖片描述
\quad其實還有種字符串哈希,然後用二分對每個字母去查找左右最長能形成迴文串的邊界,時間複雜度爲O(nlogn)O(nlogn),感興趣的小夥伴們可以試試啦~

發佈了222 篇原創文章 · 獲贊 99 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章