針對最長回文子串相關的問題,馬拉車演算法應該是比較通用的解法,今天我們就來具體看看這個演算法。
馬拉車演算法(manacher『s algorithm)是用來查詢乙個字串的最長回文子串的線性方法,由乙個叫 manacher 的人在2023年發明的,這個方法的最大貢獻是在於將時間複雜度提公升到了線性。而馬拉車演算法的主要思路是維護乙個跟原字串 str 長度一樣的陣列 lens,lens[i] 表示以 str[i] 為中點的回串其中一邊的長度。
在這裡,有的人把中點算進去,有的人記錄兩邊的長度,其實都是一樣的。我在這裡是只記錄一邊的長度,不包括中點。比如cdcde:
str: [c, d, c, d, e]lens: [0, 1, 1, 0, 0]
那麼 lens 裡最大的自然就對應最長回串的中點了。所以這個演算法的核心就是如何快速計算 lens。
回文有奇偶長度兩種情況,通過補充間隔符可以將這兩種情況化簡為奇數長度。
比如:
aba補充為^#a#b#a#$,中點還是 b。 abba補充為^#a#b#b#a#$,中點為 #,最後可以去掉。針對間隔符,首先要確保在字串中不會出現,這裡我是確保字串中不會出現^、#、$。
原字串中每乙個字元都會被#包圍,這樣就確保現在的字串長度一定是奇數。
至於在開頭增加^,在結尾增加$,這樣是為了確保從任意乙個位置開始檢查回文時,一定會遇到不一樣的時候,從而退出迴圈。而且也方便我們計算原字元的下標,直接除以2即可。
寫法是:
string str = "cbcbccde"; char t = new char[str.length() * 2 + 3]; t[0] = '^'; t[1] = '#'; t[t.length - 1] = '$'; for (int i = 0; i < str.length(); i++)
這就是演算法的關鍵了,它充分利用了回文串的對稱性。
我們用 c 表示回文串的中心,用 r 表示回文串的右邊半徑。所以 r = c + p[ i ] 。c 和 r 所對應的回文串是當前迴圈中 r 最靠右的回文串。用 i_mirror 表示當前需要求的第 i 個字元關於 c 對應的下標。
讓我們考慮求 p [ i ] 的時候:
我們可以利用回文串 c 的對稱性。i 關於 c 的對稱點是 i_mirror ,p [ i_mirror ] = 3,所以 p [ i ] 也等於 3 。
但需要考慮特殊情況:
p [ i_mirror ] + i >= r
如下圖:
當我們要求 p [ i ] 的時候,p [ mirror ] = 7,而此時 p [ i ] 並不等於 7 ,為什麼呢,因為我們從 i 開始往後數 7 個,等於 22 ,已經超過了最右的 r ,此時不能利用對稱性了,但我們一定可以擴充套件到 r 的,所以 p [ i ] 至少等於 r - i = 20 - 15 = 5,會不會更大呢,我們只需要比較 t [ r+1 ] 和 t [ r+1 ]關於 i 的對稱點就行了,就像中心擴充套件法一樣乙個個擴充套件。
i_mirror - p [ i_mirror ] == 0
如下圖:
此時p [ i_mirror ] = 1,但是 p [ i ] 賦值成 1 是不正確的,出現這種情況的原因是 p [ i_mirror ] 在擴充套件的時候首先是 "#" == "#" ,之後遇到了 "^"和另乙個字元比較,也就是到了邊界,才終止迴圈的。而 p [ i ] 並沒有遇到邊界,所以我們可以繼續通過中心擴充套件法一步一步向兩邊擴充套件就行了。
c 和 r 的更新
既然知道如何計算長度陣列了,那最關鍵的 c 和 r 到底什麼時候需要更新呢?
i + p [ i ] > r時,也就是當求出的 p [ i ] 的右邊界大於當前的 r 時,我們就需要更新 c 和 r 為當前的回文串了。因為我們必須保證 i 在 r 裡面,所以一旦有更右邊的 r 就要更新 r。
假設我們要寫乙個方法,傳入引數是原字串s,返回值是各個字元對應的最長回文子串長度陣列,那麼具體方法就是:
public int calsubstrings(string s) // 存放新的內容 char content = new char[s.length() * 2 + 3]; // 開頭用^ content[0] = '^'; // s中的每乙個字元要用#包圍 content[1] = '#'; for (int i = 0; i < s.length(); i++) // 結尾用$ content[content.length - 1] = '$'; // 當前的回文串中心下標 int center = 0; // 當前的回文串右邊界 int right = 0; // 儲存以每乙個位置為中心,所能獲得的最長回文子串的長度 int maxlength = new int[content.length]; // 首尾兩個字元沒有必要計算 for (int index = 1; index < content.length - 1; index++) // 正常求解,向左右擴充套件 while (content[index + (maxlength[index] + 1)] == content[index - (maxlength[index] + 1)]) // 如果當前index對應的右邊界,比現有的right大 if (index + maxlength[index] > right) } // 最終的結果 int result = new int[s.length()]; for (int i = 0; i < s.length(); i++) return result; }
以上就是我關於馬拉車演算法的理解,用來解決最長回文子串的問題,簡直就是一把利器。 動態規劃 最長回文子串
動態規劃 最長回文子串 題目描述 給出乙個字串s,求s的最長回文子串的長度 樣例 字串 patzjujztaccbcc 的最長回文子串為 atzjujzta 長度為9。動態規劃思想 令dp i j 表示s i 至s j 所表示的子串是否是回文子串,是則為1,不是為0。這樣根據s i 是否等於s j ...
動態規劃 最長回文子串
給定乙個字串 s,找到 s 中最長的回文子串。你可以假設 s 的最大長度為 1000。示例 1 輸入 babad 輸出 bab 注意 aba 也是乙個有效答案。示例 2 輸入 cbbd 輸出 bb 本題有很多種解法,最簡單的暴力求解,但是會超時。下面分別說明動態規劃法和中心擴散法。解法一 動態規劃法...
最長回文子串 動態規劃
給出乙個字串s,求s的最長回文子串的長度。樣例輸入 patzjujztaccbcc 輸出 9 尋找二維動態規劃表示式dp i j 如果直接用dp i j 表示子符串從s i 到s j 的最長回文子串長度無法得出遞推表示式。令dp i j 表示s i 至s j 所表示的子串是否是回文子串,是則為1,不...