Manacher 馬拉車算法
首先感謝 https://www.cnblogs.com/grandyang/p/4475985.html 這篇文章,給了我很大幫助,解釋的很詳細。
最近在學習lyd的算法競賽書,學到求最長回文串的時候就看到了O(n)復雜度的Manacher算法,書上給的是hash+二分做法,復雜度為O(nlgn),所以我就去學習了一下Manacher算法.
上面大佬的文章比較詳細了,我在這裏再說一下我當時比較迷惑地方,以及我註意的一些細節 我這個人就是和細節過不去,一點細節上解釋不明白我就感覺自己還是不會
如何計算數組 p
一般的方法,是以中心點為中心,挨個將半徑逐步擴張,直至字符串不再是回文字符串。但是這樣做,整體的算法復雜度為 O(n^2)。馬拉車算法的關鍵之處,就在於巧妙的應用了回文字符串的性質,來計算數組 p。
馬拉車算法在計算數組 p 的整個流程中,一直在更新兩個變量:
(1)id:回文子串的中心位置
(2)mx:回文子串的最後位置
使用這兩個變量,便可以用一次掃描來計算出整個數組 p,關鍵公式為:
p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;
我們用圖示來理解這個公式,如下圖:
當前,我們已經得到了 p[0...i-1],想要計算出 p[i] 來。紅1為以 j 為中心的回文子串,紅2為以 i 為中心的回文子串,紅3為以 id 為中心的回文子串(首尾兩端分別為mx的對稱點和mx)。
那麽,如果 mx 在 i 的右邊,則我們可以通過已經計算出的 p[j] 來計算 p[i],其中 j 與 i 的中心點為 id。這裏分兩種情況:
(1) 先直接令 p[i] 的回文子串就等於 p[j] 的回文子串,即紅2長度等於紅1,然後判斷紅2的末尾是否超過了 mx,如果沒有超過,則說明 p[i] 就等於 p[j]。
為什麽呢?
因為以 id 為中心的回文子串為紅3,包含了紅1和紅2,而且紅1和紅2以 id 為中心,那麽一定有紅2=紅1。並且已經知道,紅1是以 j 為中心的最長子串,那麽紅2也肯定是以 i 為中心的最長子串。
(2)如果紅2的末尾超過了 mx,那麽就只能讓 p[i] = mx - i了,即我可以保證至少半徑到 mx 這個位置,是可以回文的,但是一旦往右超出了 mx,就不能保證了,剩下的只能用笨方法慢慢擴張來得到最長回文子串。
那如果紅2的左邊超出了mx的對稱點,怎麽辦?不會出現這種情況的,因為紅1的右邊不會超過mx。如果超過了mx,那麽在上一次叠代中,id應該更新為j,mx應該更新為 j+p[j]。在叠代中,會始終保證 mx 是所有已經得到的回文子串末端最靠右的位置。
另外,如果 mx 不在 i 的右邊呢?那就利用不了紅3的對稱性了,只能使用笨方法慢慢擴張了。
mx更新為i+p[i] 所以此時mx指的是目前最長回文串的下一個字符,因此 p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1 中不用把mx-i改為mx-i+1 ,當時這個地方還有點不解呢
string manacher(string s)
{
string tmp = "$#";
for (int i = 0; i < s.size(); i++)
{
tmp += s[i]; //插入
tmp += "#";
}
int len = tmp.size();
vector<int> p(len, 0);
int mx = 0, id = 0, resLen = 0, resCenter = 0;
for (int i = 1; i < len; i++) // <len就是當 i=len-1 時 p[i]+i 就是字符串最後一個字符‘\0‘
{
p[i] = mx > i ? min(mx - i, p[2 * id - i]) : 1;
while (tmp[i + p[i]] == tmp[i - p[i]]) p[i]++;
if (i+p[i] > mx)
{
mx = p[i]; //更新回文串最右端 也就是目前最長回文串
id = i;
}
if (p[i] > resLen)
{
resLen = p[i];
resCenter = i;
}
}
return s.substr((resCenter-resLen)/2,resLen-1);
}
Manacher 馬拉車算法