1. 程式人生 > >資料結構與演算法隨筆之------最長迴文子串四種方法求解(暴力列舉+動態規劃+中心擴充套件+manacher演算法(馬拉車))

資料結構與演算法隨筆之------最長迴文子串四種方法求解(暴力列舉+動態規劃+中心擴充套件+manacher演算法(馬拉車))

所謂迴文串,就是正著讀和倒著讀結果都一樣的迴文字串。
比如:
a, aba, abccba都是迴文串,
ab, abb, abca都不是迴文串。

一、暴力法

方法一:直接暴力列舉

求每一個子串時間複雜度O(N^2), 判斷子串是不是迴文O(N),兩者是相乘關係,所以時間複雜度為O(N^3)。

#include <iostream>
using namespace std;

string longestPalindrome(string &s)
{
    int len = s.size();                  //字串長度
    int maxlen = 1;                      //最長迴文字串長度
    int start = 0;                       //最長迴文字串起始地址
    for(int i = 0; i < len; i++)         //起始地址
    {
        for(int j = i + 1; j < len; j++) //結束地址
        {
            int tmp1 = i, tmp2 = j;
            while(tmp1 < tmp2 && s.at(tmp1) == s.at(tmp2))//判斷是不是迴文
            {
                tmp1++;
                tmp2--;
            }

            if(tmp1 >= tmp2 && j - i + 1 > maxlen)
            {
                maxlen = j - i + 1;
                start = i;
            }
        }
    }

    return s.substr(start, maxlen);
}

int main()
{
    string s;
    cout << "Input source string: ";
    cin >> s;
    cout << "The longest palindrome: " << longestPalindrome(s);
    return 0;
}

此外補充一種非常好的方法:
分析:有兩種可能,一種是迴文字串的長度為奇數,一種是偶數的情況。i為字串當前字元的下標。
當迴文字串為奇數的時候,j表示i-j與i+j構成的迴文字串長度;當迴文字串長度為偶數的時候,j表示i+1左邊j個字元一直到i右邊j個字元的迴文字串長度~~~

用maxvalue儲存遍歷結果得到的最大值並且輸出~~

這種演算法思路非常清晰且易懂

#include <bits/stdc++.h>
using namespace std;

int main()
{
    string ss;
    getline(cin,ss);
    int temp,maxvalue=0;
    int len = ss.size()
    for(int i = 0; i < len; i++)
    {
        temp = 1;
        ///迴文串為奇數
        for(int j = 1; j < len; j++)
        {
            if(i - j < 0 || i + j >= len || s[i - j] != s[i + j])
                break;
            temp += 2;
        }
        ///迴文串為偶數
        maxvalue = temp > maxvalue ? temp : maxvalue;
        temp = 0;
        for(int j = 1; j < len; j++)
        {
            if(i - j + 1 < 0 || i + j >= len || s[i - j + 1] != s[i + j])
                break;
            temp += 2;
        }
        maxvalue = temp > maxvalue ? temp : maxvalue;
    }
    cout << maxvalue;
}

方法二:動態規劃


下面介紹動態規劃的方法,使用動態規劃可以達到最優的 O(n2) 複雜度。

  令 dp[i][j] 表示 S[i] 至 S[j] 所表示的子串是否是迴文子串,是則為 1,不是則為 0。這樣根據 S[i] 是否等於 S[j] ,可以把轉移情況分為兩類:

 若 S[i] == S[j],那麼只要 S[i+1] 至 S[j-1] 是迴文子串,S[i] 至 S[j] 就是迴文子串;如果S[i+1] 至 S[j-1] 不是迴文子串,則 S[i] 至 S[j] 也不是迴文子串。
 若 S[i] != S[j],那麼 S[i] 至 S[j] 一定不是迴文子串。    
  由此可以寫出狀態轉移方程:

          dp[i][j]={dp[i+1][j−1],S[i]==S[j]0,S[i]!=S[j]dp[i][j]={dp[i+1][j−1],S[i]==S[j]0,S[i]!=S[j]

  邊界:dp[i][i]=1,dp[i][i+1] = (S[i] == S[i+1]) ? 1 : 0。

  根據遞推寫法從邊界出發的原理,注意到邊界表示的是長度為 1 和 2 的子串,且每次轉移時都對子串的長度減了 1,因此不妨考慮按子串的長度和子串的初始位置進行列舉,即第一遍將長度為 3 的子串的 dp 值全部求出,第二遍通過第一遍結果計算出長度為 4 的子串的 dp 值 ……

 演算法時間複雜度為O(N ^ 2)。

/*
    最長迴文子串 
*/
 
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>
#include <stdbool.h>
 
#define maxn 1010
char S[maxn];
int dp[maxn][maxn];                 
 
int main() {
    gets(S);                        // 輸入整行字元 
    int len=strlen(S), ans=1;        // ans 記錄最長迴文子串長度 
    int i, j, L;        
    // 邊界 
    for(i=0; i<len; ++i) {            
        dp[i][i] = 1;
        if(i < len-1) {
            if(S[i] == S[i+1]) {
                dp[i][i+1] = 1;
                ans = 2;
            }
        }
    }
    // 狀態轉移方程 
    for(L=3; L<=len; ++L) {            // 列舉子串長度 
        for(i=0; i+L-1 < len; ++i) {    // 列舉子串的起始節點 
            j = i+L-1;                // 子串的右端結點 
            if(S[i]==S[j] && dp[i+1][j-1]==1) {
                dp[i][j] = 1;
                ans = L;            // 更新最長迴文子串長度 
            }
        }
    }
    printf("%d\n", ans);            // 輸出 
 
    return 0;
}

方法三:中心擴充套件法

中心擴充套件就是把給定的字串的每一個字母當做中心,向兩邊擴充套件,這樣來找最長的子迴文串。演算法複雜度為O(N^2)。
需要考慮兩種情況:
長度為奇數的迴文串,比如a, aba, abcba
長度為偶數的迴文串,比如aa, abba

給出演算法程式碼,關於此題實現還沒來得及寫。

#include <iostream>
#include <cstring>
using namespace std;

string longestPalindrome(string &s)
{
    const int len = s.size();
    int maxlen = 1;
    int start = 0;

    for(int i = 0; i < len; i++)//求長度為奇數的迴文串
    {
        int j = i - 1, k = i + 1;
        while(j >= 0 && k < len && s.at(j) == s.at(k))
        {
            if(k - j + 1 > maxlen)
            {
                maxlen = k - j + 1;
                start = j;
            }

            j--;
            k++;
        }
    }

    for(int i = 0; i < len; i++)//求長度為偶數的迴文串
    {
        int j = i, k = i + 1;
        while(j >= 0 && k < len && s.at(j) == s.at(k))
        {
            if(k - j + 1 > maxlen)
            {
                maxlen = k - j + 1;
                start = j;
            }

            j--;
            k++;
        }
    }

    return s.substr(start, maxlen);
}


int main()
{
    string s;
    cout << "Input source string: ";
    cin >> s;
    cout << "The longest palindrome: " << longestPalindrome(s);
    return 0;
}

作者:海天一樹X
連結:https://www.jianshu.com/p/c82cada7e5b0
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。

方法四:Manacher演算法 (馬拉車)

實現程式碼

#include <bits/stdc++.h>
using namespace std;
const int maxn =1e6;
string str;
string s_new;
int len[maxn<<1];
int init(string st)
{
    int len = st.size();
    s_new='$';
    for(int i =1; i <= 2*len; i+=2)
    {
        s_new += '#';
        s_new += st[i/2];
    }
    s_new+='#';
    s_new+='\0';
    return 2*len+1;// 返回 s_new 的長度

}
int Manacher(string st,int len_)
{
    int mx = 0,ans = 0,po =0;//mx即為當前計算迴文串最右邊字元的最大值
    for(int i =1; i <= len_ ; i++)
    {
        if(mx>i)
            len[i]=min(mx-i,len[2*po-i]);
        else
            len[i]=1;//如果i>=mx,要從頭開始匹配
        while(st[i-len[i]]==st[i+len[i]])
            len[i]++;
        if(len[i]+i>mx)//若新計算的迴文串右端點位置大於mx,要更新po和mx的值
        {
            mx = len[i]+i;
            po = i;
        }
        ans = max(ans,len[i]);//返回Len[i]中的最大值-1即為原串的最長迴文子串額長度
    }
    return ans  - 1;
}
int main()
{
    getline(cin,str);
    int l = init(str);
    cout<<Manacher(s_new,l)<<endl;
    return 0;
}