1. 程式人生 > >Manacher’s Algorithm超詳細!!!

Manacher’s Algorithm超詳細!!!

0x00 問題描述

給定一個字串,找到最長的迴文子串。

  • 如果給定的字串是“forgeeksskeegfor”,則輸出應為“geeksskeeg”
  • 如果給定的字串是“abaaba”,則輸出應為“abaaba”
  • 如果給定的字串是“abababa”,則輸出應為“abababa”
  • 如果給定的字串是“abcbabcbabcba”,則輸出應為“abcbabcba”

0x01 常見解法

尋找回文的一種方法是從字串的中心開始,逐個比較左右兩個方向上的字元。如果兩側(中心的左側和右側)的相應字元匹配,那麼它們將成為迴文。舉個例子,對於字串“abababa”

這裡字串的中心是第4個字元(索引3b。如果我們匹配中心左右兩側的字元,則所有字元都匹配,因此字串“abababa”是迴文。

這裡的中心位置不僅是實際的字串字元位置,而且也可以是兩個字元之間的位置。考慮偶數長度的字串“abaaba”。 該字串中心在第3和第4個字元aa之間。

要找到長度為$N$的字串的最長迴文子串,一種方法是取每個可能的$2 N + 1$箇中心($N$個字元位置,兩個字元之間的$N-1$個位置和左右兩個邊界位置),對於每個中心,分別從左右方向上匹配字元並跟蹤LPS。 這種方法時間複雜度是$O(N ^ 2)$。

0x02 Manacher 演算法

讓我們考慮兩個字串“abababa”“abaaba”

在這兩個字串中,中心位置(第一字串中的位置7和第二字串中的位置6)的左側和右側是對稱的。為什麼?因為整個字串是圍繞中心位置的迴文串。

如果我們需要從左到右計算每個 2 N + 1 2 N + 1

個位置的最長迴文子串,那麼迴文的對稱性可以幫助避免一些不必要的計算(即字元比較)。如果在任何位置 P P 都有一些長度為 L L 的迴文,那麼我們可能不需要在位置 P + 1 P + 1 處比較左側和右側的所有字元。我們已經在 P P 之前的位置計算了LPS,它們可以幫助避免位置 P P 之後的一些比較。

我們來看看字串“abababa”,它有15箇中心位置。我們需要計算每個位置的最長迴文串的長度。

  • 在位置0處,根本沒有LPS(左側沒有要比較的字元),因此LPS的長度將為0

  • 在位置1處,LPS是a,因此LPS的長度將為1

  • 在位置2處,根本沒有LPS(左和右字元ab不匹配),因此LPS的長度將為0

  • 在位置3處,LPS是aba,因此LPS的長度將是3

  • 在位置4處,根本沒有LPS(左和右字元ba不匹配),因此LPS的長度將為0

  • 在位置5處,LPS是ababa,因此LPS的長度為5

    … 等等。

我們將所有這些迴文長度儲存在一個數組中,比如說 L L 。然後字串S和LPS長度 L L​ 如下所示:

同樣,字串“abaaba”的LPS長度 L L 將如下所示:

在LPS陣列中:

  • 奇數位置(實際字元位置)的LPS長度值將為奇數且大於或等於1(如果在其左側和右側沒有其他匹配項,則1將來自中心字元本身)
  • 偶數位置的LPS長度值(兩個字元之間的位置,最左側和右側位置)將是偶數且大於或等於 0 0 (當左側和右側沒有匹配時將出現 0 0

字串的位置和索引是兩個不同的東西。對於長度為 N N 的給定字串 S S ,索引將是從 0 0 N 1 N-1 (總 N N 個索引),並且位置將是從 0 0 2 N 2N (總共 2 N + 1 2 N + 1 個位置)。

LPS長度值可以用兩種方式解釋,一種是索引,另一種是位置。位置 I I 處的LPS值 d d L [ i ] = d L [i] = d )表示:

  • 從位置 i d i-d i + d i + d 的子串是長度為 d d 的迴文(就位置而言)
  • 從索引 ( i d ) / 2 (i-d)/ 2 [ ( i + d ) / 2 1 ] [(i + d)/ 2 - 1] 的子串是長度為d的迴文(就索引而言)

例如在字串“abaaba”中, L [ 3 ] = 3 L [3] = 3 表示從位置 0 0 3 3 3-3 )到 6 6 3 + 3 3 + 3 )長度為 3 3 的迴文子字串“aba”,它也可以表示為索引 0 0 [ ( 3 3 ) / 2 ] [ (3-3)/ 2] 2 [ ( 3 + 3 ) / 2 1 ] 2 [(3 + 3)/ 2 - 1] 長度為 3 3 的的迴文子字串“aba”。現在主要任務是怎麼有效地計算LPS陣列。 一旦計算出該陣列,字串 S S 的LPS將是以LPS陣列中最大LPS長度值的位置為中心。

0x0201 計算LPS陣列

為了有效地計算LPS陣列,我們需要解決的問題就是後面需要計算LPS長度的位置如何與先前已經計算LPS長度的位置相關聯

對於字串“abaaba”

當我們計算到第 3 3 個位置:

  • 位置 2 2 和位置 4 4 處的LPS長度值相同
  • 位置 1 1 和位置 5 5 處的LPS長度值相同

我們從位置 0 0 開始從左到右計算LPS長度值,因此我們已經知道位置 1 1 2 2 3 3 處的LPS長度值,那麼我們就不需要計算位置 4 4 5 5 處的LPS長度,因為它們是等於位置3左側相應位置的LPS長度值。

當我們計算到第 6 6 個位置:

  • 位置 5 5 和位置 7 7 處的LPS長度值相同

  • 位置 4 4 和位置 8 8 處的LPS長度值相同

    …等等。

如果我們已經知道位置 1 , 2 , 3 , 4 , 5 1,2,3,4,5 6 6 處的LPS長度值,那麼我們就不需要計算位置 7 , 8 , 9 , 10 7,8,9,10 11 11 處的LPS長度,因為它們等於位置 6 6 左側相應位置的LPS長度值。

接著考慮字串“abababa”

如果我們已經知道位置 1 , 2 , 3 , 4 , 5 , 6 1,2,3,4,5,6 7 7 處的LPS長度值,那麼我們就不需要計算位置 8 , 9 , 10 , 11 , 12 8,9,10,11,12 13 13 處的LPS長度,因為它們等於位置 7 7 左側相應位置的LPS長度值。

你能看出為什麼在字串“abaaba”中的位置 3 , 6 , 9 3,6,9 周圍的LPS長度值是對稱的嗎?那是因為這些位置周圍有一個迴文子串。對於字串“abababa” 7 7 這個中心位置也是如此。

在迴文串中心位置附近的LPS長度值是否總是對稱的(相同)?答案是否定的。

我們看字串“abababa”中的位置 3 3 11 11 ,兩個位置都具有LPS長度 3 3 。但是位置 1 1 5 5 (位置 3 3 兩側)不對稱。類似地,位置 9 9 13 13 (位置 11 11 兩側)不對稱。

此時,我們可以看到,在以某個位置為中心的迴文串左右,圍繞中心位置的LPS長度值可能對稱也可能不對稱。如果我們能夠知道什麼時候左右位置的LPS長度是對稱,我們就可以不用計算右側位置的LPS長度,因為它將與已知的左側相應位置的LPS值完全相同。

0x0202 引數

讓我們先介紹一些術語:

  • centerPosition - 這是計算LPS長度的起始位置,假設centerPosition的LPS長度為d(即L [centerPosition] = d

  • centerRightPosition - 距離centerPosition右側長度d(即centerRightPosition = centerPosition + d

  • centerLeftPosition - 距離centerPosition的左側長度d(即centerLeftPosition = centerPosition - d

  • currentRightPosition - 這是centerPosition右側的位置,LPS長度未知

  • currentLeftPosition - 這是centerPosition左側的位置,對應於currentRightPosition

    centerPosition - currentLeftPosition = currentRightPosition - centerPosition

    currentLeftPosition = 2*centerPosition - currentRightPosition

  • i-left palindrome - 位於centerPosition的左側,以currentLeftPosition為中心的迴文串

  • i-right palindrome - 位於centerPosition的右邊,以currentRightPosition為中心的迴文串

  • center palindrome - 以centerPosition為中心的迴文串

假設我們處於已知LPS長度的centerPosition,並且同時我們知道所有小於centerPosition位置的LPS長度。假設此時centerPosition的LPS長度為 d d ,即L[centerPosition] = d,這意味著位置centerPosition-dcenterPosition+d之間的子串是一個迴文串。現在我們繼續計算大於centerPosition位置的LPS長度。假設我們在currentRightPosition> centerPosition),我們需要知道此處的LPS長度。為此,我們檢視已計算的currentLeftPosition的LPS長度。如果currentLeftPosition的LPS長度小於centerRightPosition - currentRightPosition,則currentRightPosition的LPS長度將等於currentLeftPosition的LPS長度。這是第一種情況

讓我們考慮字串“abababa”d:

當我們計算到位置7的LPS長度時,其中L[7]=7,如果我們將位置7視為centerPosition,則centerLeftPosition將為0並且centerRightPosition將為14。現在我們需要計算centerPosition右側其它位置的LPS長度。對於currentRightPosition=8currentLeftPosition6並且L[currentLeftPosition]=0centerRightPosition - currentRightPosition = 14 - 8 = 6,恰好是第一種情況,因此L[currentRightPosition] = L[8] = 0。對於第10和第12位同樣適用,因此,L[10] = L[4] = 0L[12] = L[2] = 0。如果我們看第9位,那麼currentRightPosition=9centerRightPosition - currentRightPosition = 14 - 9 = 5,這裡L[currentLeftPosition] = centerRightPosition - currentRightPosition,所以第一種情況不適用於此處。另外要注意的是,centerRightPosition是輸入字串的結束位置,這意味著中心迴文串是輸入字串的字尾。在這種情況下,L[currentRightPosition] = L[currentLeftPosition]。這就是第二種情況

9 , 11 , 13 9,11,13 14 14 號位置適用於第二種情況,因此:L[9] = L[5] = 5L[11] = L[3] = 3L[13] = L[1] = 1L[14] = L[0] = 0

第一種和第二種情況本質的不同是什麼?當一個較大長度的迴文串結構包含一個位於其自身中心左側的較小長度迴文串時,那麼基於對稱性質,將會有另一個相同的較小長度的迴文串位於較大長度的迴文串的右側。如果左側的較小回文串不是較大回文串的字首,則是第一種情況,如果它是字首並且較大回文串是輸入字串本身的字尾,則是第二種情況。

如果當前的中心迴文串(center palindrome)完全包含左側迴文串並且左側迴文串不是中心迴文串的字首(第一種情況)或(如果我左迴文是中心迴文的字首)如果中心迴文串是整個字串的字尾(第二種情況),那麼在當前中心右側(i-right palindrome)的最長迴文串與當前中心(i-left palindrome)左側的最長迴文串一樣長。為什麼呢?

左側迴文串不能比相應的右側迴文串更長,這個很好理解,因為右邊是從左邊得到的。那麼為什麼右側迴文串不能比左