文章相關**已收錄至我的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 題意在給定的字串中找...