什麼是馬拉車演算法?

2021-09-24 12:14:13 字數 3502 閱讀 9313

文章相關**已收錄至我的github,歡迎star:lsylulu/myarticle

有這樣乙個問題,給定乙個字串,返回最長回文的子串的長度?要求時間複雜度為o(n)。

正常情況下我們會這麼做,先將字串進行特殊處理比如11311處理成#1#1#3#1#1#,然後遍歷每個索引,找最長回文數。結果/2為正確答案。

那麼,如何找每個索引的最長回文數呢?

通常情況下是設定乙個start和end變數,start向右走,end向左走,每走一步比較start與end指向的元素是否相等,並且注意一下越界情況。start-end便是回文長度。但是,這種演算法的時間複雜度為o(n^2)。能不能設計乙個t=o(n)的演算法呢?答案是manacher演算法。

r--記錄回文最右邊界。隨著字元陣列的遍歷,r的值必然不會減小,要麼與上一次相等,要麼被推向右邊。

來看乙個例子助於理解:

a b d b a b d b a

0 1 2 3 4 5 6 7 8

剛開始,r的值預設是-1。隨著陣列的遍歷,r的值將做如下變化。r=-1->0->1->4->4->8->8->8->8->8

c--回文中心。c與r是相輔相成的,c是r對應的回文中心,r一旦改變,c也會跟著變化。剛開始,c的值預設也是-1。繼續上個例子c的值將做如下變化。c=-1->0->1->2->2->4->4->4->4->4

curarr[ ]--長度與給定字串相同(準確來說是'#'處理後的字串)記錄每乙個索引的最大回文長度。

i--當前遍歷的索引。

i』--i關於c的對稱點。

對於每乙個索引,都有相應的c和r。我們所要做的就是知道該索引後,給出currarr[i]的粗略值,根據這個粗略值將currarr[i]精確計算。再完善c與r的值。

每一次的遍歷,大致分為兩種情況:

1)i在r的外部

遇到這種情況,r一定會往右走的。先讓curarr[i]=1(也就是說把自己作為回文中心,且回文長度是自己),然後嘗試向右擴乙個,看看是否滿足以第i個為中心的回文。滿足則curarr[i]++,不滿足則下來確定c與r的值,此時c必然是i,r=i+curarr[i]。

2)i在r的內部

對於這種情況,先讓curarr[i]=curarr[i'],然後再開始試探性的往兩邊擴,如果相同則curarr[i]++,不同則下來確定c與r的值。以i為中心的回文右邊界超過了r時,c=i,r=i的右邊界。

先讓curarr[i]=r-i,然後再試探性的擴,具體步驟與上一種情況一樣。

先讓curarr[i]=r,再試探性的擴,具體步驟與上一種情況一樣。

流程大概理清楚了,我們來結合我的詳細注釋看看具體**。

首先需要處理給定的字串,因為會有奇回文和偶回文的問題。

/**

* 處理原始字串使之更方便操作

* @param str

* @return

*/public static char manacherstring(string str)

return res;

}

然後再求處理後的最長回文數,返回的是回文半徑-1,剛好就是最長回文的長度。

/**

* 返回str的最長回文子串的長度

* @param str

* @return

*/public static int maxlcpslength(string str)

char chararr = manacherstring(str);

int curarr = new int[chararr.length];

//回文中心

int c = -1;

//回文右邊界

int r = -1;

int max = integer.min_value;

for (int i = 0; i != chararr.length; i++)

else

}//滿足條件則設定回文中心和右邊界

c=i;

r=i+curarr[i];

max = math.max(max, curarr[i]);

}//i在回文右邊界內

else

else }}

//統計回文半徑

if (i + curarr[i] > r)

max = math.max(max, curarr[i]);

}return max - 1;

}

仔細看**會發現,當i在回文右邊界內的這種情況,之前分析可以分3中情況,但我合併了一下。原因是處理的細節幾乎一樣,只有在剛開始curarr[i]的賦值上是不同的。強行分開必然會冗餘。

我們再利用上述條件,利用三目運算子簡化**,得到下面的精簡版:

/**

* 返回str的最長回文子串的長度

* @param str

* @return

*/public static int maxlcpslength(string str)

char chararr = manacherstring(str);

//回文半徑陣列

int curarr = new int[chararr.length];

//當前回文中心的索引

int c = -1;

//當前回文右邊界

int r = -1;

int max = integer.min_value;

for (int i = 0; i != chararr.length; i++)

else

}//統計回文半徑

if (i + curarr[i] > r)

max = math.max(max, curarr[i]);

}return max - 1;

}

manacher演算法是一致公認的解字串回文的最佳演算法,我們回頭思考一下,為什麼manacher能夠加速判斷?

原因是充分利用遍歷過索引的最長回文半徑的資訊,減少中心點後續回文的判斷長度。由於遍歷到i時,i'的最長回文半徑是已知的,所以可以確定i的回文半徑一定大於等於i'的回文半徑與r(整體回文右邊界)-i(當前索引)的最小值。

給定乙個有回文的字串,只能向字串右邊新增字元,如何在新增最少的情況下是整個字串變成回文串?

利用manacher的思想,在從左到右依次遍歷,當有乙個索引的回文右邊界剛好與字串右邊界相等時,將回文左邊界的子串逆序之後新增到字串的後面。具體實現就不貼啦!

馬拉車演算法

思路筆記 上述情況1和情況2又可以歸結為 i 的回文半徑 和 r i的距離 中小的那個就是i的回文半徑。include include includeusing namespace std string manacherstring string str return res int min int...

馬拉車演算法

馬拉車演算法是一種計算最長回文子串的演算法,以其優秀的線性複雜度聞名於世,相較於o n 2 o n 2 o n2 的dpdp dp演算法和會被特殊資料卡到o n 2 o n 2 o n2 的暴力演算法,馬拉車演算法無疑是求解最長回文子串的最優選擇。最長回文子串分為偶數串和奇數串,為了避免這些問題,馬...

馬拉車演算法

manacher char s maxn 1 int n,hw maxn 1 int l maxn 1 r maxn 1 void manacher char a n len 2 2 s n 0 int maxr 0,m 0 for int i 1 i n i manacher 題意在給定的字串中找...