Manacher‘s Algorithm,O(n)时间找出最长回文子串。
我们将以此字符串作为例子讲解
ABABBDDABBBBDD。
Manacher's Alogorithm第一步是将其作以下扩展:
$ # A # B # A # B # B # D # D # A # B # B # B # B # D # D # &
其中尾字符用于告知已到字符串尾。
我们首先对上述字符串人工求出最长回文串,我们只计算包括自身向右扩展的部分,结果如下:
$ # A # B # A # B # B # D # D # & step 1
1 2 1 4 1 2 1 2 3 2 1 2 3 2 1
Manacher's Algorithm 首先通过step 1将上述字符串进行扩展,并用一个数组p,来保存上面我们口算的最长回文子串。
首先,该算法借助2个辅助变量,分别为mx和id, 用来表示当前求得的最长的回文子串的最右边界及当前最长回文子串的中心点。即有关系以下公式: mx = id + p[id]。 即当前最长回文子串的中心点为id,向右扩展p[id]即可到达边界mx。问题是,如何计算 p[i] 呢?
1. 当 i < mx 时, 有p[i] >= min(p[ 2 * id - i], mx - i)。 这里的2 * id - i 即为上图的 j ,为 i 关于id的对称点。为什么呢?
- 首先,显然,由于最长回文子串是以id为中心,长度为从中心向右扩展到mx的序列暂且称为pstr。
- 如果以 j 为中心的回文子串包含在pstr中,那么显然由于 i 是 j 关于id的对称点,那么此时肯定有p[i] >= p[j]。其实只能是p[i] = p[j]。这也就是上图的情况,i和j的小下划线分别说明包含在pstr中的小回文串。
- 如果pstr不能完全包含以 j 为中心的回文串呢? 因为此时 i 还不知道从mx 开始扩展的情况,但是由于堆成,肯定以 i 为中心的回文串的p[i]肯定大于等于 mx - i。也就是如下图的情况,绿色框框表示 i 至少当前这段是回文串。
2. 当 i 大于mx 时,由于没有先验知识,所以此时p[i]只能为1。 注意,这里i之前的p[x] 都是已知的。我们是要求i和 i 之后的p[x]。
class Solution { private: string preprocess(const string& s) { string res = "$#"; for(int i = 0; i < s.size(); ++ i) { res += s[i]; res += '#'; } res += '^'; return res; } public: string longestPalindrome(string s) { const int len = s.size(); if(len <= 1) return s; // 获得举例的string string str = preprocess(s); // Manncher algorithm, O(n) int id = 0, mx = 0; int sz = str.size(); // 用于保存p[x] vector<int> p(sz, 0); for(int i = 1; i < sz - 1; ++ i) { // 关键在这里 p[i] = mx > i ? min(p[2*id-i], mx - i) : 1; // 首先肯定的是p[i]内一定是字符串,那么还是需要继续扩展的 // 直到扩展到不满足的情况。 while(str[i + p[i]] == str[i - p[i]]) p[i] ++; // 更新 mx 和 id if(i + p[i] > mx) { mx = i + p[i]; id = i; } } // 求出最大的id 和 mx int maxLen = 0, idx = 0; for(int i = 1; i < sz - 1; ++ i) { if(p[i] > maxLen) { maxLen = p[i]; idx = i; } } return s.substr((idx - maxLen) / 2, maxLen - 1); } };
参考:
JustDoIt 博客, http://www.cnblogs.com/TenosDoIt/p/3675788.html