如何高效的統計最長迴文子串的長度? Manacher馬拉車算法通俗講解與實踐教程

待解決問題描述

給定一個字符串abcbddacaddx要求輸出這個字符串的最長迴文子串的長度。

解釋:子串是指從這個字符串任意地方截取一段任意長度的字符串。迴文子串指的是前面所提到的截取出的子串中是對稱的那種子串。比如“bcb”就是對稱。

如果我們是要求出最長迴文子串那麼有一個簡單的方式就是將原字符串逆序,然後匹配原字符串與逆序後的字符串之間最長公共子串。但是現在我們只需要統計最長迴文子串的長度。所以我們不需要知道具體是哪段字符串。只需要知道長度。我們需要計算的信息量大大降低,這就意味着有更快速的解決方案。一般是用Manacher算法。

Manacher算法的思想(馬拉車算法)

注意:我們只需要求得一個長度值。是最長迴文子串的長度值。不需要求具體是哪段子串。瞭解這個信息可以幫助我們理解馬拉車算法是如何節省計算量的。

馬拉車算法的思想非常簡單。它就是從左往右遍歷計算出以當前元素爲中心點所能向周圍拓展的迴文子串最大半徑。比如“abcb”中的"c"的最大拓展半徑是2.仔細一想其實“以當前元素爲中心點所能向周圍拓展的迴文子串”這句話是有一點點問題的。比如:“bccb”中的第一個“c”似乎以他爲中心拓展的迴文串並不是關於它對稱的,本質的原因就是因爲這個迴文子串的字符個數是偶數。只有當迴文子串中的字符個數是奇數纔會關於中心元素對稱。爲何將回文子串的字符個數變成奇。Manacher就想了一個辦法將每個元素中間插入一個原字符串中不存的字符。比如“#”。

舉個例子:

  • “bccb”各個字符中間插入一個#就變成了“#b#c#c#b#”,可以看到原先的偶數長度迴文串變成了奇數長度迴文串。
  • “bcb” 各個字符中間插入一個“#”就變成了“#b#c#b#”,可以看到原先奇數長度的迴文串仍然還是奇數長度迴文串。

前面介紹的是一些預處理。現在開始介紹馬拉車算法的核心思想。它的本質在於重複利用之前已經算出的那些字符的最大拓展長度。

注意:它是從左往右遍歷計算出當前字符的最大拓展長度。這意味着當它遍歷到某個字符時,它左邊的那些字符的最大拓展長度已經計算出來了,它右邊的那些字符最大拓展長度並沒有計算出。馬拉車算法就是充分利用了左邊已經計算出的那些字符的最大拓展長度來節省計算量的

馬拉車算法在計算某個字符的最大拓展長度時候已知兩個量:

  1. 當前字符左邊那些各個字符的最大拓展長度
  2. 左邊那些字符爲中心的迴文子串中,能到達最右邊的那個迴文串的中心字符下標

根據“能到達最右邊的那個迴文串的中心字符下標”就能知道當前字符是否屬於“能到達最右邊的那個迴文串”的一部分。如果是的話根據對稱性就能夠知道當前字符串的最少能夠拓展的半徑。

記各個字符最大拓展長度的數組爲len[]。能到達最右邊的那個迴文串的中心字符下標記爲far_index.當前字符的下標爲i。
因此能到達最右邊的那個迴文串的最右端的那個字符下標爲:far_index+len[far_index]-1。如果far_index+len[far_index]-1i大證明當前字符是屬於能到達最右邊的那個迴文串的一部分。當前字符的最少拓展長度是可以根據它的對稱元素的最大拓展長度算出來。當前字符下標是i,因此它關於far_index的左邊那個對稱點的下標爲:2*far_index-i。這個字符的最大拓展長度是已知的。根據對稱性當前字符最小拓展長度是min(len[2*far_index-i], far_index+len[far_index]-i)。然後當前元素最大拓展長度那就得根據這個最小拓展長度繼續往外拓展統計最大長度。

最終的我們可以知道各個迴文子串的長度。注意這個字符串中間是夾雜了“#”的。我們得到最終的最長迴文字符串長度是需要去掉這個“#”。

比如第i個元素最大拓展長度是len[i]。如果它是“#”那麼它對應迴文子串長度爲(len[i]-1).如果不是“#”那麼它對應的迴文子串長度爲(len[i]-1)*2+1.
Manacher馬拉車算法的c++代碼如下所示:

string s = "#d#a#a#";
	vector<int> len(s.size(), 1);
	int far_index = 0;
	int max_index =0;
	int max_len = 0;
	for (int i = 0; i < s.size(); i++)
	{
		if (far_index + len[far_index] > i)
		{
			len[i] = min(len[2 * far_index - i], far_index + len[far_index]-i);
		}
		while (i - len[i]>=0 && i + len[i]<s.size() && s[i + len[i]]==s[i-len[i]])
			len[i]++;

		if (len[i] > max_len)
		{
			max_len = len[i];
			max_index = i;
		}
	}

	if (s[max_index] != '#')
		cout << (max_len-1)/2*2+1;
	else
		cout << max_len - 1;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章