对于字符串S, 要找到它最长的回文子串,能想到的最暴力方法,应该是对于每个元素i-th都向左向右对称搜索,最后用一个数组span 记录下相对应元素i-th为中心的回文子串长度。
那么问题来了:
1. 这样的方法,对于奇回文子串和偶回文子串的处理不一样,比如所“acbca” 和“acbbca”
2. 计算冗余,e.g. ”sscssabcccchccccba“中, 自左向右遍历计算的话,会发现, ”abcccchccccba“是对称的,而这个回文字符串里又左右对称分别包含了两个回文子字符”cccc“和”cccc“, 在对第二个”cccc“字符串遍历的时候,其实可以利用到”abcccchccccba“的对称性来直接赋值,而不用再次计算
于是,引出了Manacher's 算法:
1. 为了可以对奇偶回文字符串不加区分地处理,对输入字符串增加边界元素(输入字符串S长度为N -> S2长度为2*N+1):e.g. ”aabbbcc“ -> "#a#a#b#b#b#c#c#" , ”aabbcc“ -> "#a#a#b#b#c#c#"
2. 对于S2字符串,自左向右,每个字符逐个计算和处理
1)用span[maxInputStringLength*2+1]数组来记录每个元素i-th为中心的回文字符串长度 : 初始化 span[0] = 0
2)用center 和 rightBoundary 来记录上一个已知的回文字符串中心index和最右边界index :初始化 center = 0, rightBoundary = 0
3)用 i 表示当前处理的元素, i2 表示 以 center 为中心的 center左侧 i 对称 元素下标 : i2 = center - (i - center)= center * 2 - i
4)加上已知的回文子串信息之后,假设已经处理了S2[0, i-1]范围的元素,那么已知的回文子串中心center 必然属于[0, i-1]区间, 但是对于rightBoundary和i的关系可以有三种情况:
a)当前处理的元素(index = i)以 center为中心,对称的元素(index = i2)上有,span[i2] < rightBoundary - i - 1, 表示i2点的回文字符串最左端不会超过leftBoundary的长度,利用了已知的回文字符串信息辅助说明,当前以i为中心的回文字符串将和i2完全对称,span[i] = span[i2]
b)当前处理的元素(index = i)以 center为中心,对称的元素(index = i2)上有,span[i2] >= rightBoundary - i - 1, 表示 i2 点的回文字符串最左端已经超过了leftBoundary的范围,不能利用已知回文字符串直接赋值,但还是可以利用对称性,使得对称搜索从rightBoundary处开始,这样可以减少计算
c)当前处理的元素(index = i)并不在已知回文串的记录范文内,那么久没有辅助信息,需要从i元素的左右对称搜索,得到span[i]的值
5)最后要更新center和rightBoundary的值,使得已知回文字符串的覆盖范围右移,这样可以持续辅助搜索
1 const int maxStringLength = 1000; 2 3 string longestPalindrome(string s) { 4 5 if (s.empty()) return ""; 6 7 ///add boundary 8 string s2; 9 for (int i = 0; i < s.size(); i++) { 10 s2.push_back('#'); 11 s2.push_back(s[i]); 12 } 13 s2.push_back('#'); 14 15 16 /// setting 17 int span[maxStringLength] = {0}; 18 span[0] = 0; // init the first element's length of palindrome 19 int center = 0, rightBoundary = 0; 20 int left = 0, right = 0; 21 22 ///traverse 23 for (int i = 1; i < s2.size(); i++) { 24 if (i <= rightBoundary) { 25 int i2 = center * 2 - i;// center - (i - center) 26 if (span[i2] < (rightBoundary - i - 1)) { 27 span[i] = span[i2]; 28 left = -1; 29 } 30 else { 31 span[i] = rightBoundary - i; 32 right = rightBoundary + 1; 33 left = i * 2 - right; //i - (right - i) 34 } 35 } 36 else { 37 span[i] = 0; 38 left = i - 1; 39 right = i + 1; 40 } 41 42 while (left >= 0 && right < s2.size() && s2[left] == s2[right]) { 43 span[i] ++; 44 left--; 45 right++; 46 } 47 48 if ((i + span[i]) > rightBoundary) { 49 center = i; 50 rightBoundary = i + span[i]; 51 } 52 } 53 54 /// find the max span length 55 int maxSubstringCenter = 0, maxSubstringLen = 0; 56 for (int i = 0; i < s2.size(); i++) { 57 if (span[i] > maxSubstringLen) { 58 maxSubstringLen = span[i]; 59 maxSubstringCenter = i; 60 } 61 } 62 63 /// remove boundary '#' from substring 64 string result; 65 for (int i = maxSubstringCenter - maxSubstringLen; i <= maxSubstringCenter + maxSubstringLen; i++) { 66 if (s2[i] != '#') 67 result.push_back(s2[i]); 68 } 69 70 return result; 71 }