時間複雜度O(n)
算法的主要思想是從左到右處理字符串,求每個位置爲中心的兩端對稱的最大半徑。
void Manacher(char s[],int n,int radius[])
{
int i,j;
for(i=1;i<n;i++) radius[i]=1;
for(i=2;i<n;i=j)
{
while(s[i-radius[i]]==s[i+radius[i]]) radius[i]++; //求以當前位置爲中心的最大對稱半徑
int rBnd=i+radius[i];
for(j=i+1;j<rBnd;j++) //對當前位置半徑覆蓋範圍內右邊的位置進行處理
{
/*
** 如果位置j以位置i爲中心的對稱位置k=i-(j-i),它的最大半徑被i的半徑包含,那麼j的最大半徑就等於radius[k],根據對稱性;
** 否則,也就是位置k的最長迴文串的左端等於或者向左超過以i爲中心的迴文串的左端,那麼j的最大半徑至少爲i+radius[i]-j;
** 這樣,i~j中間位置的最大半徑都求出來了,i下次直接從j開始擴展最大半徑
*/
if(j+radius[i-(j-i)]<rBnd)
radius[j]=radius[i-(j-i)];
else
{
radius[j]=rBnd-j;
break;
}
}
}
}
雖然表面是雙重循環,但我們分析一下,由於循環內部是每次從位置i擴展到下一個位置j,一直擴展到位置n,如果把外層循環,一層一層展開接起來,那就是常數倍從1到n的運算,所以總起來的運算量是O(n)的。
網上較多的是下面這種寫法:
void Manacher(intrad[],char str[],int n)
{
int i,mx=0,id;
for(i=1;i<n;i++)
{
if(mx>i) rad[i]=min(rad[2*id-i],mx-i);
else rad[i]=1;
for(;str[i+rad[i]]==str[i-rad[i]];rad[i]++)
;
if(mx<rad[i]+i)
{
mx=rad[i]+i;
id=i;
}
}
}
首先:大家都知道什麼叫回文串吧,這個算法要解決的就是一個字符串中最長的迴文子串有多長。這個算法可以在O(n)的時間複雜度內既線性時間複雜度的情況下,求出以每個字符爲中心的最長迴文有多長,
這個算法有一個很巧妙的地方,它把奇數的迴文串和偶數的迴文串統一起來考慮了。這一點一直是在做迴文串問題中時比較煩的地方。這個算法還有一個很好的地方就是充分利用了字符匹配的特殊性,避免了大量不必要的重複匹配。
算法大致過程是這樣。先在每兩個相鄰字符中間插入一個分隔符,當然這個分隔符要在原串中沒有出現過。一般可以用‘#’分隔。這樣就非常巧妙的將奇數長度迴文串與偶數長度迴文串統一起來考慮了(見下面的一個例子,迴文串長度全爲奇數了),然後用一個輔助數組P記錄以每個字符爲中心的最長迴文串的信息。P[id]記錄的是以字符str[id]爲中心的最長迴文串,當以str[id]爲第一個字符,這個最長迴文串向右延伸了P[id]個字符。
原串: 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
這裏有一個很好的性質,P[id]-1就是該回文子串在原串中的長度(包括‘#’)。如果這裏不是特別清楚,可以自己拿出紙來畫一畫,自己體會體會。當然這裏可能每個人寫法不盡相同,不過我想大致思路應該是一樣的吧。
好,我們繼續。現在的關鍵問題就在於怎麼在O(n)時間複雜度內求出P數組了。只要把這個P數組求出來,最長迴文子串就可以直接掃一遍得出來了。
由於這個算法是線性從前往後掃的。那麼當我們準備求P[i]的時候,i以前的P[j]我們是已經得到了的。我們用mx記在i之前的迴文串中,延伸至最右端的位置。同時用id這個變量記下取得這個最優mx時的id值。(注:爲了防止字符比較的時候越界,我在這個加了‘#’的字符串之前還加了另一個特殊字符‘$’,故我的新串下標是從1開始的)
好,到這裏,我們可以先貼一份代碼了。