Manacher’s Algorithm

Posted freering

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Manacher’s Algorithm相关的知识,希望对你有一定的参考价值。

最长的回文字符串第二部分

原文为英文页面,地址:https://articles.leetcode.com/longest-palindromic-substring-part-ii/

给定一个字符串S,找到S中最长的回文子字符串。

注意:
这是文章的第二部分:最长回文子串在这里,我们描述了一个算法(Manacher算法),它可以在线性时间内找到最长的回文子串。请阅读第一部分了解更多背景信息。

在我以前的文章中,我们总共讨论了四种不同的方法,其中有一个非常简单的算法,运行时间为O(N 2),空间复杂度稳定。在这里,我们讨论在O(N)时间和O(N)空间中运行的算法,也称为Manacher算法。

提示:
想想你会如何改进简单的O(N 2)方法。考虑最糟糕的情况。最糟糕的情况是多个回文相互重叠的输入。例如,输入:“aaaaaaaaa”和“cabcbabcbabcba”。事实上,我们可以利用回文的对称性并避免一些不必要的计算。

一个O(N)解决方案(Manacher算法):
首先,我们通过在字母之间插入特殊字符‘#‘来将输入字符串S转换为另一个字符串T. 这么做的原因很快就会很快清楚。

例如:S =“abaaba”,T =“#a#b#a#a#b#a#”。

为了找到最长的回文子串,我们需要在每个T i周围扩展,使得T i-d ... T i + d形成回文。你应该马上看到d是以T i为中心的回文的长度

我们将中间结果存储在数组P中,其中P [i]等于在T i处的回文中心的长度最长的回文子串将成为P中的最大元素。

使用上面的例子,我们填充P如下(从左到右):

T = # a # b # a # a # b # a #
P = 0 1 0 3 0 1 6 1 0 3 0 1 0

看着P,我们立即看到最长的回文是“abaaba”,如P 6 = 6所示。

您是否注意到通过在字母之间插入特殊字符(#),这两个长度均为偶数的回文是否被优雅地处理?(请注意:这是为了更容易地演示这个想法,并不一定需要对算法进行编码。)

现在,想象你在回文中心“abaaba”绘制一条想象的垂直线。你有没有注意到P中的数字是围绕这个中心对称的?不仅如此,请尝试使用另一个回文“aba”,这些数字也反映了类似的对称性。这是巧合吗?答案是肯定的,不是。这仅仅是一个条件,但无论如何,我们有很大的进步,因为我们可以消除P [i]的重新计算部分。

让我们继续讨论一个稍微复杂的例子,其中有更多的重叠回文,其中S =“babcbabcbaccba”。

 

技术分享图片
上图显示T由S =“babcbabcbaccba”转化而来。假设你达到了表P部分完成的状态。垂直的实线表示回文“abcbabcba”的中心(C)。两条虚线垂直线分别表示其左(L)和右(R)边缘。您处于索引i处,并且围绕C的镜像索引是i‘。你如何有效地计算P [i]?

 

假设我们已经到达指数i = 13,并且我们需要计算P [13](由问号?表示)。我们首先看一下它在回文中心C周围的镜像索引i‘,索引i‘= 9。

 

技术分享图片
上面的两条绿色实线表示以i和i‘为中心的两个回文序列覆盖的区域。我们看一下C周围的镜像索引,它是索引i‘。P [i‘] = P [9] = 1.由于回文在其中心附近具有对称性,因此P [i]也必须为1。

 

正如你在上面看到的那样,P [i] = P [i‘] = 1是非常明显的,由于回文中心周围的对称性,这一定是正确的。事实上,C之后的所有三个元素都遵循对称性(即P [12] = P [10] = 0,P [13] = P [9] = 1,P [14] = P [8] = 0)。

 

技术分享图片
现在我们处于索引i = 15,其C上的镜像索引是i‘= 7。P [15] = P [7] = 7?

 

现在我们处于索引i = 15处。P [i]的价值是什么如果我们遵循对称性,P [i]的值应该与P [i‘] = 7相同,但这是错误的。如果我们在T围绕中心展开15,它形成回文“a#b#a#b#a”,这实际上是比什么是它的对称对应显示短。为什么?

 

技术分享图片
在索引i和i‘中央围绕着彩色线条。绿色实线表示由于C周围的对称性而必须匹配两侧的区域。红色实线表示可能不匹配两侧的区域。虚线绿色线条显示穿过中心的区域。

 

很显然,由两条实线表示的区域中的两个子串必须完全匹配。中心区域(用绿色虚线表示)也必须是对称的。注意P [i‘]是7,并且它一直扩展到回文的左边缘(L)(用红色实线表示),它不再落在回文的对称属性之下。我们所知道的是P [i] ≥5,并且为了找到P [i]的实际值,我们必须通过扩展右边缘(R)来进行字符匹配。在这种情况下,由于P [21]≠P [1],我们得出结论P [i] = 5。

我们来总结一下这个算法的关键部分,如下所示:

 
if P[ i’ ] ≤ R – i,
then P[ i ] ← P[ i’ ]
else P[ i ] ≥ P[ i’ ].(我们必须扩展到右边缘(R)以找到P [i]。

看看它有多优雅?如果你能够充分掌握上述总结,那么你已经获得了这个算法的本质,这也是最难的部分。

最后一部分是确定我们应该在何时将C的位置与R一起移动到右侧,这很容易:

 

如果以i为中心的回文右端扩展到R,我们将C更新为i(这个新回文的中心),并将R延伸到新回文的右边。

在每一步中,都有两种可能性。如果P [i]≤R-i,我们将P [i]设置为P [i‘],这只需要一步。否则,我们试图通过从右边缘R开始将回文中心改为i。扩展R(内部while循环)最多总共需要N步,并且定位和测试每个中心总共需要N步太。因此,该算法确保在至多2 * N步完成,给出线性时间解决方案。

 

// Transform S into T.
// For example, S = "abba", T = "^#a#b#b#a#$".
// ^ and $ signs are sentinels appended to each end to avoid bounds checking
string preProcess(string s) {
  int n = s.length();
  if (n == 0) return "^$";
  string ret = "^";
  for (int i = 0; i < n; i++)
    ret += "#" + s.substr(i, 1);
 
  ret += "#$";
  return ret;
}
 
string longestPalindrome(string s) {
  string T = preProcess(s);
  int n = T.length();
  int *P = new int[n];
  int C = 0, R = 0;
  for (int i = 1; i < n-1; i++) {
    int i_mirror = 2*C-i; // equals to i‘ = C - (i-C)
    
    P[i] = (R > i) ? min(R-i, P[i_mirror]) : 0;
    
    // Attempt to expand palindrome centered at i
    while (T[i + 1 + P[i]] == T[i - 1 - P[i]])
      P[i]++;
 
    // If palindrome centered at i expand past R,
    // adjust center based on expanded palindrome.
    if (i + P[i] > R) {
      C = i;
      R = i + P[i];
    }
  }
 
  // Find the maximum element in P.
  int maxLen = 0;
  int centerIndex = 0;
  for (int i = 1; i < n-1; i++) {
    if (P[i] > maxLen) {
      maxLen = P[i];
      centerIndex = i;
    }
  }
  delete[] P;
  
  return s.substr((centerIndex - 1 - maxLen)/2, maxLen);
}

 

注意:
这个算法绝对不是微不足道的,在面试过程中你不会想到这样的算法。不过,我希望您喜欢阅读本文,希望它能帮助您理解这个有趣的算法。如果你走了这么远,你应该得到一个掌声!??

进一步思考:

  • 事实上,这个问题存在第六种解决方案 - 使用后缀树。然而,它并不像这个那样高效(运行时间O(N log N)而且更多的开支在构建后缀树上)并且实现起来更加复杂。如果你有兴趣,请阅读维基百科有关最长回文子串的文章
  • 如果您需要找到最长的回文序列,该怎么办?(你知道子串和子序列之间的区别吗?)

有用的链接:
» Manacher的算法O(N)时间求字符串的最长回文子串(最好的解释,如果你可以读中文)
»一个简单的线性时间算法寻找最长的回文子串
»寻找回文
»寻找最长的回文在线性时间的子串
»维基百科:最长的回文子串

以上是关于Manacher’s Algorithm的主要内容,如果未能解决你的问题,请参考以下文章

Manacher's Algorithm(马拉车算法)

leetcode 5. 最长回文子串 (Manacher's Algorithm)

Manacher's Algorithm ----马拉车算法

Manacher's Algorithm 马拉车算法

什么是马拉车算法(Manacher's Algorithm)?

leetcode 算法 之 马拉松算法(Manacher's algorithm)(未完成)