【轉載】最長迴文字符串(manacher算法)

原文轉載自:http://blog.csdn.net/lsjseu/article/details/9990539
偶然看見了人家的博客發現這麼一個問題,研究了一下午, 才發現其中的奧妙。Stupid。

題目描述:
迴文串就是一個正讀和反讀都一樣的字符串,比如“level”或者“noon”等等就是迴文串。
迴文子串,顧名思義,即字符串中滿足迴文性質的子串。
給出一個只由小寫英文字符a,b,c…x,y,z組成的字符串,請輸出其中最長的迴文子串的長度。

輸入:
輸入包含多個測試用例,每組測試用例輸入一行由小寫英文字符a,b,c…x,y,z組成的字符串,字符串的長度不大於200000。

輸出:
對於每組測試用例,輸出一個整數,表示該組測試用例的字符串中所包含的的最長迴文子串的長度。

樣例輸入:

abab
bbbb
abba

樣例輸出:

3
4
4

思路:

迴文串包括奇數長的和偶數長的,一般求的時候都要分情況討論,這個算法做了個簡單的處理把奇偶情況統一了。原來是奇數長度還是奇數長度,偶數長度還是偶數長度。

算法的基本思路是這樣的,把原串每個字符中間用一個串中沒出現過的字符分隔#開來(統一奇偶),同時爲了防止越界,在字符串的首部也加入一個特殊符$,但是與分隔符不同。同時字符串的末尾也加入’\0’.

算法的核心:用輔助數組p記錄以每個字符爲核心的最長迴文字符串半徑。也就是p[i]記錄了以str[i]爲中心的最長迴文字符串半徑。p[i]最小爲1,此時迴文字符串就是字符串本身。

  先看個例子:

  原串:        w aa bwsw f d
  新串:     $ # w# a # a # b# w # s # w # f # d #

輔助數組P: 1 2 1 2 3 2 1 2 1 2 1 4 1 2 1 2 1 2 1

首先看代碼(藉助http://blog.csdn.net/thyftguhfyguj/article/details/9531149):

 #include <stdio.h>    
    #include <iostream>  
    using namespace std;  

    char s[200002];    
    char str[400010];    
    int p[400010];    

    int min(int a,int b){    
        return a < b ? a : b;    
    }    

    int pre(){    
        int i,j = 0;    
        str[j++] = '$';//加入字符串首部的字符串    
        for(i = 0;s[i];i++){    
            str[j++] = '#';  //分隔符  
            str[j++] = s[i];    
        }    
        str[j++] = '#';    
        str[j] = '\0';  //尾部加'\0'  
        cout<<str<<endl;  
        return j;    
    }    

    void manacher(int n){    
        int mx = 0,id,i;    
        p[0] = 0;    
        for(i = 1;i < n;i++){    
            if(mx > i)  //在這個之類可以藉助前面算的一部分  
                p[i] = min(mx - i,p[2 * id - i]); //p[2*id-i]表示j處的迴文長度   
            else  //如果i大於mx,則必須重新自己算  
                p[i] = 1;    
            while(str[i - p[i]] == str[i + p[i]])  //算出迴文字符串的半徑  
                p[i]++;    
            if(p[i] + i > mx){  //記錄目前回文字符串擴展最長的id  
                mx = p[i] + i;    
                id = i;    
            }    
        }    
    }    


    int main(int argc, char const *argv[]){    

        while(scanf("%s",s) != EOF){    
            int n = pre();    
            manacher(n);    
            int ans = 0,i;    
            for(i = 1;i < n;i++)    
                if(p[i] > ans)    
                    ans = p[i];    
            printf("%d\n",ans - 1);         
        }    
        return 0;    
    }   

上面的程序說明:pre()函數對給定字符串進行預處理,也就是加分隔符。

上面幾個變量說明:id記錄具有遍歷過程中最長半徑的迴文字符串中心字符串。mx記錄了具有最長迴文字符串的右邊界。看下面這個圖(注意,j爲i關於id對稱的點,j = 2*id - i):
這裏寫圖片描述

但是p[i] = p[j]是沒有錯的,但是這裏有個問題,就是i的一部分超出陰影部分,這就不對了。請看下圖(爲了看得更清楚,下面子串用細條紋表示):
這裏寫圖片描述

此時,根據對稱型只能得出p[i]和p[j]紅色陰影部分是相等的,這就爲什麼有取最小值這個操作:

   if(mx > i)  //在這個之類可以藉助前面算的一部分  
        p[i] = min(mx - i,p[2 * id - i]);  

下面代碼就很容易看懂了。

最後遍歷一遍p數組,找出最大的p[i]-1就是所求的最長迴文字符串長度,下面證明一下:
(1)因爲p[i]記錄插入分隔符之後的迴文字符串半徑,注意插入分隔符之後的字符串中的迴文字符串肯定是奇數長度,所以以i爲中心的迴文字符串長度爲2*p[i]-1。

例如:

bb=>#b#b#
bab=>#b#a#a#b#

2)注意上面兩個例子的關係。#b#b#減去一個#號的長度就是原來的2倍。即((2*p[i]-1)-1)/2 = p(i)-1,得證。

算法的有效比較次數爲MaxId 次,所以說這個算法的時間複雜度爲O(n)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章