1. 程式人生 > >最長迴文子串-Manacher演算法(詳解)

最長迴文子串-Manacher演算法(詳解)

定義:

迴文串:一個字串, 逆置之後,與原串相同;
迴文子串: 一個字串的子串(連續),是迴文串.則該子串為整個字串的一個迴文子串.
最長迴文子串:一個字串中最長的迴文子串.

求最長迴文子串最容易

方法1(dp):

先將串逆置,再與原串求最長公共子序列(LCS)(o(n^2)), //時間O(n^2) 空間O(f(n^2));

方法2(純暴力):

兩重迴圈列舉起點終點(所有子串)(o(n^2)),如果是迴文串,返回長度,不是返回0(o(n)),尋找最大長度.//時間 O(n^3);

稍微提升一下,

方法3(中心擴充套件):

列舉對稱中心, 向兩邊擴充套件,遇邊界,或不同,停止擴充套件,繼續下一個對稱中心,之後找最大//O(n^2);

方法4(dp):

暴力改進: 輔助陣列記錄從i到j是否迴文
P(i,j)= P(i+1,j-1)(如果S[i] = S[j])。類動規方程。
P(i,i)= 1,P(i,i+1)= if(S[i]= S[i+1]) 個人感覺沒LCS好寫,好想.

下面進入正題,傳說ManacherO(n),你信嗎? 反正我不信.

我看了下, 別人的部落格上來就說要分奇數偶數,然後加’#’,然後誰小於等於誰,然後貼程式碼.
所以決定認真寫下本篇部落格.解除疑惑.
想寫程式碼,首先要懂道理,看別人程式碼,花了好長時間看懂了,昂,是這麼一回事,等讓自己寫原題時,能憑印象寫出來,但是為什麼題目稍微變一變就不會了?理解的不透徹,不深入,一定要完全弄懂道理後,自己將演算法轉化為程式碼,自己寫模板,再看別人怎麼寫的,這樣寫有什麼優點,再感悟,再理解,更新自己的模板,這也是鄙人的學習演算法的方法.

不扯了.

首先manacher來自於中心擴充套件思想,所以首先要把中心擴充套件理解透徹.這裡給出中心擴充套件的程式碼,分奇數偶數.下面寫下虛擬碼,思路已經很清晰了,列舉中心位置,找到最長,更新最長.

//char a[N];
//cin>>a; 
int find()
{
    int maxlen = 0, len = strlen(a);
//--------------------------奇數-------------------------------------------
    for(int i = 0; i < len; i++)
    {
        int
l = i-1, r = i+1; while(l >=0 && r <= len-1 && a[l] == a[r]) { if(r-l + 1 > maxlen) maxlen = r-l+1; l--; r++; } } //------------------------------------------------------------------------- //--------------------------偶數------------------------------------------- for(int i = 0; i < len; i++) { int l = i, r = i+1; while(l >= 0 && r <=len-1 && a[r]==a[l]) { if(r-l+1 > maxlen) maxlen = r-l+1; l--; r++; } } //------------------------------------------------------------------------- return maxlen; }

可以在上述程式碼中加上若干printf語句除錯,,有沒有感覺到,上述程式碼做了很多沒必要的比較,? 好,優化!
首先是時候對奇數偶數說一聲拜拜了,中心擴充套件,首先兩個字中心!!,,偶數沒有中心.所以仔細考慮一下,利用了一個很顯然的數學道理
:奇數加偶數一定是奇數 ,偶數加奇數一定是奇數. 所以我們在字串開頭結尾新增#,每個字元中間加#,一共加了strlen()+1個 #
,最後得新字串長度:2(strlen()) + 1 必為奇數. 例:我們給他加一箇中心”aa” ——>”^a^a^” (#a#a#)
都可以,從而將偶數轉奇數 對於奇數”a” —> “#a#” 還是奇數

觀察:

原串:qwecb abcccOcccba ckpoiu
上述程式碼中每次求出的l-r+1:(列舉每個字元為中心進行擴充套件的迴文串長度) 為:
q w e c b a b c c c O c c c b a c k p o i u
1,1, 1,1,1,5,1,1,3,1,11,1,3,1,1,1,1,1,1,1,1,1 max: 11

這裡寫圖片描述

看一下其中有沒有什麼空子,可不可以偷懶.
發現,對稱位置有懶可以偷

這裡寫圖片描述

k1, k2 在s(迴文)之中,所以,以k1中間的c與以k2中間的c 為中心的迴文子串長度必然相等..

manacher的懶就偷再這了.

(1)Len陣列簡介與性質

這裡寫圖片描述

Manacher演算法用一個輔助陣列Len[i]表示以字元T[i]為中心的最長迴文字串的最右字元到T[i]的長度,比如以T[i]為中心的最長迴文字串是T[l,r],那麼Len[i]=r-i+1。
對於上面的例子,可以得出Len[i]陣列為:

這裡寫圖片描述

Len
陣列有一個性質,那就是Len[i]-1就是該回文子串在原字串S中的長度,至於證明,首先在轉換得到的字串T中,所有的迴文字串的長度都為奇數,那
麼對於以T[i]為中心的最長迴文字串,其長度就為2*Len[i]-1,經過觀察可知,T中所有的迴文子串,其中分隔符的數量一定比其他字元的數量多
1,也就是有Len[i]個分隔符,剩下Len[i]-1個字元來自原字串,所以該回文串在原字串中的長度就為Len[i]-1。

有了這個性質,那麼原問題就轉化為求所有的Len[i]。下面介紹如何線上性時間複雜度內求出所有的Len。

(2)Len陣列的計算

首先從左往右依次計算Len[i],當計算Len[i]時,Lenj已經計算完畢。設P為之前計算中最長迴文子串的右端點的最大值,並且設取得這個最大值的位置為po,分兩種情況:

第一種情況:i<=P

這裡寫圖片描述

那麼找到i相對於po的對稱位置,設為j,那麼如果Len[j] < P-i,如上圖: 那
麼說明以j為中心的迴文串一定在以po為中心的迴文串的內部,且j和i關於位置po對稱,由迴文串的定義可知,一個迴文串反過來還是一個迴文串,所以以i
為中心的迴文串的長度至少和以j為中心的迴文串一樣,即Len[i] >= Len[j]。 由對稱性可知Len[i]=Len[j]。

如果Len[j]>=P-i,由對稱性,說明以i為中心的迴文串可能會延伸到P之外,而大於P的部分我們還沒有進行匹配,所以要從P+1位置開始一個一個進行匹配,直到發生失配,從而更新P和對應的po以及Len[i]。

第二種情況: i>P

如果i比P還要大,說明對於中點為i的迴文串還一點都沒有匹配,這個時候,就只能老老實實地一個一個匹配了,匹配完成後要更新P的位置和對應的po以及Len[i]。

這裡寫圖片描述

題目:
hihocoder 1032 : 最長迴文子串

程式碼:

/*************************************************************************
        > File Name: 1032.cpp
      > Author: dulun
      > Mail: [email protected]
      > Created Time: 2016年04月10日 星期日 00時52分10秒
 ************************************************************************/

#include<iostream>
#include<stdio.h>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#define LL long long
using namespace std;

const int N = 1e6+9;
char a[N];
char t[N*2];
int cnt[N*2];

void change()
{
    int len = strlen(a);
    t[0] = '@';
    for(int i = 0; i < len; i++)
    {
        t[i*2+1] = '#';
        t[i*2+2] = a[i];
    }
    t[len*2+1] = '#';
    t[len*2+2] = '\0';
}

int manacher()
{
    memset(cnt, 0, sizeof(cnt));
    cnt[0] = cnt[1] = 1;
    int id = 1;
    int ans = 1;
    int len = strlen(t);
    for(int i = 2; i <= len; i++)
    {
        int num = min(cnt[id*2-i], cnt[id]+id-i);
        while(t[i-num] == t[i+num])
            num++;
        cnt[i] = num;
        if(id+cnt[id] < i+num)
            id = i;
        if(ans<num)
            ans = num;
    }
    return ans-1;
}

int main()
{
    int T;
    cin>>T;
    while(T--)
    {
        memset(t, 0, sizeof(t));
        memset(a, 0, sizeof(a));

        int ans;
        scanf("%s", a);
        change();
        printf("%d\n", manacher());
    }
    return 0;
}
    const int maxn=1000010;  
    char str[maxn];//原字串  
    char tmp[maxn<<1];//轉換後的字串  
    int Len[maxn<<1];  
    //轉換原始串  
    int INIT(char *st)  
    {  
        int i,len=strlen(st);  
        tmp[0]='@';//字串開頭增加一個特殊字元,防止越界  
        for(i=1;i<=2*len;i+=2)  
        {  
            tmp[i]='#';  
            tmp[i+1]=st[i/2];  
        }  
        tmp[2*len+1]='#';  
        tmp[2*len+2]='$';//字串結尾加一個字元,防止越界  
        tmp[2*len+3]=0;  
        return 2*len+1;//返回轉換字串的長度  
    }  
    //Manacher演算法計算過程  
    int MANACHER(char *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]);//在Len[j]和mx-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]);  
         }  
         return ans-1;//返回Len[i]中的最大值-1即為原串的最長迴文子串額長度   
      }  

相關推薦

Manacher演算法()

定義: 迴文串:一個字串, 逆置之後,與原串相同; 迴文子串: 一個字串的子串(連續),是迴文串.則該子串為整個字串的一個迴文子串. 最長迴文子串:一個字串中最長的迴文子串.

Manacher演算法入門

一.最長迴文子串問題與Manacher演算法. 迴文串,是一種滿足一定條件的字串,設字串S的第i位為 S [

HiHo #1032 : Manacher演算法

#1032 : 最長迴文子串 時間限制:1000ms 單點時限:1000ms 記憶體限制:64MB 描述    小Hi和小Ho是一對好朋友,出生在資訊化社會的他們對程式設計產生了莫

【HDU - 3068】Manacher演算法,馬拉車演算法

題幹: 給出一個只由小寫英文字元a,b,c...y,z組成的字串S,求S中最長迴文串的長度.  迴文就是正反讀都是一樣的字串,如aba, abba等 Input 輸入有多組case,不超過120組,每組輸入為一行小寫英文字元a,b,c...y,z組成的字串S  兩

Manacher演算法(一個字串中找到)

零、預備知識   Manacher用於在一個字串中找到最長的迴文子串。   迴文串:正著念和反著念一樣,例如aabbaa,anna等。   注意子串與子序列的區別:     子串必須是在原字元中可以找到的。比如 " I am a student"。am是子串(當然也是子序列),但是aa就不是子串了(是

HihoCode1032 manacher演算法

求最長迴文子串的演算法比較經典的是manacher演算法 轉載自這裡 首先,說明一下用到的陣列和其他引數的含義: (1)p[i] : 以字串中下標為的字元為中心的迴文子串半徑長度; 例如:abaa字串,那麼p[1]=2,(以b為中心的迴文子串是aba,半徑長度為2。計算半徑時

——Manacher 演算法​​​​​​​

0. 問題定義 最長迴文子串問題:給定一個字串,求它的最長迴文子串長度。 如果一個字串正著讀和反著讀是一樣的,那它就是迴文串。下面是一些迴文串的例項: 12321 a aba abba aaaa tattarrattat(牛津英語詞典中最長的迴文單詞) 1. Brut

Manacher馬拉車演算法求解

        馬拉車演算法是一種能在O(n)的時間複雜度範圍內得出結果。我看了不下幾次這個演算法,每次都覺得有點懂了,但是一碰到題目了,就生生寫不出來。歸根揭底,還是沒有掌握其思想。 馬拉車演算法的第一個核心思想就是往原始的字串裡填充一些輔助的東西,使得我們在考慮問題時不

-----Manacher演算法

迴文串是指aba、abba、cccbccc、aaaa這種左右對稱的字串。 輸入一個字串Str,輸出Str裡最長迴文子串的長度。 Input 輸入Str(Str的長度 <= 100000) Output 輸出最長迴文子串的長度L。 Input示例 da

51Nod 1088 ——————Manacher,馬拉車演算法

基準時間限制:1 秒 空間限制:131072 KB 分值: 0 難度:基礎題 迴文串是指&ThickSpace;aba、abba、cccbccc、aaaa\;aba、abba、cccbccc、aaaaaba、abba、cccbccc、aaaa這種左

manacher's algorithm尋找

manacher’s algorithm尋找最長迴文子串 #include <vector> #include <iostream> #include <string> using namespace std; strin

LeetCode 5. Longest Palindromic Substring Python 四種解法(Manacher 動態規劃)

Longest Palindromic Substring 最長迴文子串 學習筆記 1. Brute method 第一種方法:直接迴圈求解,o(n2)o(n2) class Solution: def longestPalindrome(self, s):

51Nod1088 /HDU3068(Manacher演算法

看到這道題我首先想到的是可以從中間向兩邊找相同的字元,分偶數和奇數迴文串長度兩種情況。不過這種方法時間複雜度很高O(n^2),應付這種水題足夠了,但是對於HDU3068那道題來說需要更好的演算法,即Ma

馬拉車演算法Manacher Algorithm)--用於計算

馬拉車演算法的目標是找到一串字串中的最長迴文子串,優點是時間複雜度為O(n) 現以尋找 “cgbaabgk” 中的最長子迴文串( “gbaabg”)為例進行說明 演算法過程(總共3步): 1.改造字串結構: 字元座標 0 1

Manacher模板】HDU 3068——求

直接做會超時,需要優化,網上通行的演算法是manacher演算法(具體原理還不是很明白),這裡可以當模板使。 // 原串最大長度N // 返回最大回文字串 res #include<cstdio> #include<cstring> #includ

Manacher演算法------求(Java)

最長迴文子串 對於一個字串,請設計一個高效演算法,計算其中最長迴文子串的長度。 給定字串A以及它的長度n,請返回最長迴文子串的長度。 測試樣例: "abc1234321ab",12 返回:7 public class Main {      public st

Manacher(模板)

該演算法就是處理一個字串中的最長迴文子串,在後綴陣列中看到過相對的解法,時間複雜度可以優化到o(n),但是相對程式碼量太大,而面對迴文串演算法有相對更簡單的方法,可以很簡單的處理出來,程式碼量小,時間也

Manacher O(n)解法+ 區間dp O(n2)解法

題解轉自部落格:www.cnblogs.com/mickole/articles/3578298.html 題目:(替代題目可去pat天梯賽練習題中尋找,當然那個題n3也能過) 長度為N(N很大)的字串,求這個字串裡的最長迴文子串?(百度2014校招筆試題目) 題目指

HDU 3068 (manacher演算法)

// // main.cpp // Richard // // Created by 邵金傑 on 16/9/27. // Copyright © 2016年 邵金傑. All rights reserved. // #include<iostream&

O(n)的方法求長度(Manacher演算法

大體思路其實就是找出一箇中心點,判斷中心點兩端的迴文串半徑是多少; 但由於找中心點的方法只適用於奇數長的迴文串,故可以在每兩個字元間插入一個間隔符來幫助結算; 用rd[i]表示以經過填充後的字串裡的第i個字元為中心,它的迴文串長度; 可以得知,在【