Manacher的算法(在线性时间内找到最长回文子串的算法)

时间:2012-05-06 04:52:14

标签: algorithm palindrome

在花了大约6-8个小时试图消化Manacher的算法后,我准备好了。但在此之前,这是最后一次在黑暗中拍摄:有人可以解释一下吗?我不关心代码。我希望有人解释 ALGORITHM

这似乎是其他人在解释算法时似乎喜欢的地方: http://www.leetcode.com/2011/11/longest-palindromic-substring-part-ii.html

我理解你为什么要把字符串转换成'#ab#'到#a#b#b#a# 之后,我失去了。例如,前面提到的网站的作者说该算法的关键部分是:

                      if P[ i' ] ≤ R – i,
                      then P[ i ] ← P[ i' ]
                      else P[ i ] ≥ P[ i' ]. (Which we have to expand past 
                      the right edge (R) to find P[ i ])

这似乎是错误的,因为当P [i'] = 7且P [i]不小于或等于R-i时,他/她在某一点上说P [i]等于5。

如果您不熟悉算法,可以使用以下链接:http://tristan-interview.blogspot.com/2011/11/longest-palindrome-substring-manachers.html(我已尝试过这个链接,但术语很糟糕且令人困惑。首先,有些内容未定义。另外,也是如此。许多变量。你需要一个清单来回忆什么变量指的是什么。)

另一个是:http://www.akalin.cx/longest-palindrome-linear-time(祝你好运)

该算法的基本要点是在线性时间内找到最长的回文。它可以在O(n ^ 2)中以最小到中等的努力量完成。这个算法应该非常“聪明”才能将其降低到O(n)。

10 个答案:

答案 0 :(得分:38)

我同意在解释链接时逻辑不正确。我在下面提供一些细节。

Manacher的算法填写表P [i],其中包含以i为中心的回文延伸的范围。如果P [5] = 3,则位置5两侧的三个字符是回文的一部分。该算法利用了这样一个事实:如果你找到了一个很长的回文,你可以通过查看左侧P的值来快速填充回文右侧的P值,因为它们应该主要是相同。

我首先解释你所谈论的案例,然后我会根据需要扩展这个答案。

R表示以C为中心的回文右侧的索引。这是您指定地点的状态:

C=11
R=20
i=15
i'=7
P[i']=7
R-i=5

逻辑是这样的:

if P[i']<=R-i:  // not true
else: // P[i] is at least 5, but may be greater

如果测试失败,链接中的伪代码表示P [i]应该大于或等于P [i'],但我认为它应该大于或等于Ri,并且解释支持那个。

由于P [i']大于Ri,以i'为中心的回文延伸经过以C为中心的回文。我们知道以i为中心的回文将至少为Ri字符宽,因为我们仍然具有对称性那一点,但我们必须明确地搜索。

如果P [i']不大于Ri,则以i'为中心的最大回文位于以C为中心的最大回文范围内,因此我们可以知道P [i]不能大于P [I']。如果是的话,我们就会有矛盾。这意味着我们能够将以i为中心的回文扩展到P [i']之外,但是如果可以的话,那么由于对称性,我们也能够将中心的回文延伸到i',但它已经是应该尽可能大。

此案例如前所述:

C=11
R=20
i=13
i'=9
P[i']=1
R-i=7

在这种情况下,P [i'] <= R-i。由于我们距离以C为中心的回文边缘仍有7个字符,因此我们知道i周围至少7个字符与i'周围的7个字符相同。由于i'周围只有一个字符回文,所以我周围也有一个字符回文。

j_random_hacker注意到逻辑应该更像这样:

if P[i']<R-i then
  P[i]=P[i']
else if P[i']>R-i then
  P[i]=R-i
else P[i]=R-i + expansion

如果P [i']&lt; R-i,然后我们知道P [i] == P [i'],因为我们仍然在以C为中心的回文范围内。

如果P [i']&gt; R-i,然后我们知道P [i] == R-i,因为否则以C为中心的回文将延伸过R.

所以扩展实际上只在P [i'] == R-i的特殊情况下才有必要,所以我们不知道P [i]的回文是否可能更长。

通过设置P [i] = min(P [i'],R-i)然后始终扩展,在实际代码中处理。这种方式不会增加时间复杂度,因为如果不需要扩展,则进行扩展所需的时间是不变的。

答案 1 :(得分:12)

这个网站上的算法似乎可以理解 http://www.akalin.cx/longest-palindrome-linear-time

要理解这种特殊方法,最好的方法是尝试解决纸上问题并抓住可以实施的技巧,以避免检查每个可能中心的回文。

首先回答你自己 - 当你找到给定长度的回文时,让我们说5 - 你不能作为下一步跳到这个回文结尾(跳过4个字母和4个中间字母)?

如果你试图制造一个长度为8的回文并放置另一个长度> 1的回文结构。 8,哪个中心位于第一个回文的右侧,你会发现一些有趣的东西。试试看: 回文长度为8 - WOWILIKEEKIL - 喜欢+ ekiL = 8 现在在大多数情况下,您可以记下两个E作为中心之间的位置,将数字8作为长度,并在最后一个L之后跳转,以寻找较大回文的中心。

这种方法不正确,较大的回文中心可以在ekiL内部,如果你在最后一个L之后跳过,你会错过它。

找到LIKE + EKIL之后,你将8放在这些算法使用的数组中,这看起来像:

[0,1,0,3,0,1,0,1,0,3,0,1,0,1,0,1,8]

[#,W,#,O,#,W,#,I,#,L,#,I,#,K,#,E,#]

诀窍是你已经知道,8之后的下一个7(8-1)数字很可能与左侧相同,所以下一步是自动从8的左边复制7个数​​字到右边的8请记住,他们还没有最终决定。 该数组看起来像这样

[0,1,0,3,0,1,0,1,0,3,0,1,0,1,0,1,8,1,0,1,0,1,0, 3](我们在8)

[#,W,#,O,#,W,#,I,#,L,#,I,#,K,#,E,#,E,#,K,#,I,#, L]

让我们举个例子,这样的跳转会破坏我们当前的解决方案,看看我们能注意到什么。

WOWILIKEEKIL - 让我们尝试在EKIL内的某个地方制作更大的回文。 但它不可能 - 我们需要将EKIL改为包含回文的东西。 什么? OOOOOh - 这就是诀窍。 在我们目前的回文的右侧有一个更大的回文中心的唯一可能性是它已经在回文的右侧(和左侧)。

让我们尝试基于WOWILIKEEKIL构建一个 我们需要将EKIL更改为例如EKIK,将I作为更大回文的中心 - 记得将LIKE更改为KIKE。 我们棘手的回文的首字母将是:

WOWIKIKEEKIK

如前所述 - 让最后一个人成为比KIKEEKIK更大的pallindrome的中心:

WOWIKIKEEKIKEEKIKIW

让我们将阵列设置为旧的pallindrom,并找出如何利用其他信息。

[_ W _ O _ W _ I _ K _ I _ K _ E _ E _ K _ I _ K _ E _ E _ K _ I _ K _ I _ W]

会的 [0,1,0,3,0,1,0,1,0,3,0,3,0,1,0,1,8

我们知道下一个I - 3号将是最长的pallindrome,但让我们暂时忘掉它。让我们将数组中的数字从8的左边复制到右边(8个数字)

[0,1,0,3,0,1,0,1,0,3,0,3,0,1,0,1,8,1,0,1,0,3,0, 3]

在我们的循环中,我们处于数字8的E之间。我不能跳到K(当前最大的pallindrome的最后一个字母),我(未来中间最大的pallindrome)有什么特别之处? 特别之处在于它超出了阵列的当前大小......怎么样? 如果你向3的右边移动3个空格 - 你就不在数组中了。这意味着它可以是最大的pallindrome的中间,你可以跳到最远的是这封信我。

对不起这个答案的长度 - 我想解释一下algorythm并且可以向你保证 - @OmnipotentEntity是对的 - 在向你解释后我理解它更好:)

答案 2 :(得分:11)

到目前为止,我已在以下链接中找到了最好的解释之一:

http://tarokuriyama.com/projects/palindrome2.php

它还具有在问题中提到的第一个链接中使用的相同字符串示例(babcbabcbaccba)的可视化。

除此链接外,我还在

找到了代码

http://algs4.cs.princeton.edu/53substring/Manacher.java.html

我希望其他人努力理解这个算法的症结会有所帮助。

答案 3 :(得分:5)

完整文章:http://www.zrzahid.com/longest-palindromic-substring-in-linear-time-manachers-algorithm/

首先让我们仔细观察回文以找到一些有趣的属性。例如,S1 =&#34; abaaba&#34;和S2 =&#34; abcba&#34;,两者都是回文,但它们之间的非平凡(即长度或字符)差异是什么? S1是以i = 2和i = 3(不存在的空间!)之间的不可见空间为中心的回文。另一方面,S2以i = 2(即c)的字符为中心。无论奇数/偶数长度如何,为了优雅地处理回文的中心,我们通过在字符之间插入特殊字符$来变换回文。那么S1 =&#34; abba&#34;和S2 =&#34; abcba&#34;将转换为T1 =&#34; $ a $ b $ a $ a $ b $ a $&#34;以i = 6和T2 =&#34; $ a $ b $ c $ b $ a $ $&#34;为中心以i = 5为中心。现在,我们可以看到中心存在且长度一致2 * n + 1,其中n =原始字符串的长度。例如,

                    i'          c           i           
      -----------------------------------------------------
      | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10| 11| 12|
      ----------------------------------------------------- 
   T1=| $ | a | $ | b | $ | a | $ | a | $ | b | $ | a | $ |
      -----------------------------------------------------

接着,从中心c周围的(变形的)回文T的对称性观察,T [c-k] = T [c + k],0 <= k <= c。即位置c-k和c + k彼此镜像。让我们换一种方式,对于中心c右侧的索引i,镜像索引i&#39;在c的左边,c-i&#39; = i-c =&gt;我&#39; = 2 * c-i,反之亦然。也就是说,

对于回文子串中心c右侧的每个位置i,i的镜像位置是,i&#39; = 2 * c-i,反之亦然。

让我们定义一个数组P [0..2 * n],使得P [i]等于以i为中心的回文长度。请注意,长度实际上是通过原始字符串中的字符数来衡量的(通过忽略特殊字符$)。同样,min和max分别是以c为中心的回文子串的最左边界和最右边界。所以,min = c-P [c]和max = c + P [c]。例如,对于回文S =&#34; abaaba&#34;,变换的回文T,镜像中心c = 6,长度数组P [0..12],min = cP [c] = 6-6 = 0, max = c + P [c] = 6 + 6 = 12和两个样本镜像索引i和i&#39;如下图所示。

      min           i'          c           i           max
      -----------------------------------------------------
      | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10| 11| 12|
      ----------------------------------------------------- 
    T=| $ | a | $ | b | $ | a | $ | a | $ | b | $ | a | $ |
      -----------------------------------------------------
    P=| 0 | 1 | 0 | 3 | 0 | 5 | 6 | 1 | 0 | 3 | 0 | 1 | 0 |
      -----------------------------------------------------

使用这样的长度数组P,我们可以通过查看P的最大元素找到最长的回文子串的长度。即,

P [i]是在变换后的字符串T中以i为中心的回文子串的长度,即。以原始字符串S的i / 2为中心;因此,最长的回文子串将是从索引开始的长度为P [i max ]的子串(i max -P [i max ]) / 2使得i max 是P中的最大元素的索引。

让我们在下面为我们的非回文示例字符串S =&#34; babaabca&#34;绘制一个类似的图。

                       min              c               max
                       |----------------|-----------------|
      --------------------------------------------------------------------
 idx= | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10| 11| 12| 13| 14| 15| 16|
      --------------------------------------------------------------------- 
    T=| $ | b | $ | a | $ | b | $ | a | $ | a | $ | b | $ | c | $ | a | $ |
      ---------------------------------------------------------------------
    P=| 0 | 1 | 0 | 3 | 0 | 3 | 0 | 1 | 4 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 |
      ---------------------------------------------------------------------

问题是如何有效地计算P.对称性质表明我们可能通过使用镜像索引i处的先前计算的P [i]来计算P [i]的以下情况。在左边,因此跳过很多计算。让我们假设我们有一个参考回文(第一回文)开始。

        
  1. 如果第二回文位于第一回文范围内,第三回文的中心位于第一回文的右侧,其长度与锚定在左侧镜中心的第二回文的长度完全相同第一个回文至少有一个字符。
    例如,在下图中,第一个回文以c = 8为中心,以min = 4和max = 12为界,第三个回文的长度以i = 9为中心(镜像索引i&#39; = 2 * ci = 7 )是,P [i] = P [i&#39;] = 1.这是因为第二回文以i&#39;为中心。是在第一回文的范围内。类似地,P [10] = P [6] = 0。
    
    
                                          |----3rd----|
                                  |----2nd----|        
                           |-----------1st Palindrome---------|
                           min          i'  c   i           max
                           |------------|---|---|-------------|
          --------------------------------------------------------------------
     idx= | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10| 11| 12| 13| 14| 15| 16|
          --------------------------------------------------------------------- 
        T=| $ | b | $ | a | $ | b | $ | a | $ | a | $ | b | $ | c | $ | a | $ |
          ---------------------------------------------------------------------
        P=| 0 | 1 | 0 | 3 | 0 | 3 | 0 | 1 | 4 | ? | ? | ? | ? | ? | ? | ? | ? |
          ---------------------------------------------------------------------
    
    现在,问题是如何检查这种情况?注意,由于段[min..i&#39;]的对称属性长度等于段[i..max]的长度。另外,请注意,如果第2回文的左边缘位于第1回文的左边界内,则第2回文完全在第1回文范围内。那是,
    
            i'-P[i'] >= min
            =>P[i']-i' < -min (negate)
            =>P[i'] < i'-min 
            =>P[i'] < max-i [(max-i)=(i'-min) due to symmetric property].
    
    结合案例1中的所有事实,
    P [i] = P [i&#39;],iff(max-i)&gt; P [i&#39;]
  2.     
  3. 如果第二回文符合或超出第一回文的左边界,则第三回文保证至少具有从其自身中心到第一回文的右外侧字符的长度。这个长度从第二回文的中心到第一回文的左最外面的字符是相同的。
    例如,在下图中,以i = 5为中心的第二回文延伸超出第一回文的左边界。所以,在这种情况下,我们不能说P [i] = P [i&#39;]。但是第三回文的长度以i = 11为中心,P [i]至少是从其中心i = 11到以c为中心的第一回文的右边界max = 12的长度。即,P [i]> = 1。这意味着,当且仅当下一个直接字符超过最大匹配与镜像字符完全匹配时,第三个回文可以延伸超过最大值,并且我们继续进行此检查。例如,在这种情况下P [13]!= P [9]并且它不能被扩展。所以,P [i] = 1。
                                                        
                  |-------2nd palindrome------|   |----3rd----|---?    
                           |-----------1st Palindrome---------|
                           min  i'          c           i   max
                           |----|-----------|-----------|-----|
          --------------------------------------------------------------------
     idx= | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10| 11| 12| 13| 14| 15| 16|
          --------------------------------------------------------------------- 
        T=| $ | b | $ | a | $ | b | $ | a | $ | a | $ | b | $ | c | $ | a | $ |
          ---------------------------------------------------------------------
        P=| 0 | 1 | 0 | 3 | 0 | 3 | 0 | 1 | 4 | 1 | 0 | ? | ? | ? | ? | ? | ? |
          ---------------------------------------------------------------------
    
    那么,如何检查这种情况?这只是案例1的失败检查。也就是说,第二回文将延伸超过第一回文iff的左边缘,
    
            i'-P[i'] < min
            =>P[i']-i' >= -min [negate]
            =>P[i'] >= i'-min 
            =>P[i'] >= max-i [(max-i)=(i'-min) due to symmetric property]. 
    
    也就是说,P [i]至少是(max-i)iff(max-i)P [i]> =(max-i),iff(max-i) 现在,如果第三个回文确实超出了最大值,那么我们需要更新新回文的中心和边界。
    如果以i为中心的回文确实扩展到最大值,那么我们有新的(扩展的)回文,因此在c = i时有一个新的中心。将max更新到新回文的最右边界。
    结合案例1和案例2中的所有事实,我们可以提出一个非常漂亮的小公式:
    
            Case 1: P[i] = P[i'],  iff (max-i) > P[i']
            Case 2: P[i]>=(max-i), iff (max-i) = min(P[i'], max-i). 
    
    即,当第三回文不能延伸超过最大值时,P [i] = min(P [i&#39;],max-i)。否则,我们在c = i的中心处有新的第三回文并且新的max = i + P [i]。
  4. 第一和第二回文都没有提供信息来帮助确定第四回文的回文长度,第四回文的中心位于第一回文的右侧之外。
    也就是说,如果i> max,我们无法预先确定P [i]。那是,
    P [i] = 0,iff max-i&lt; 0
    结合所有案例,我们得出以下公式:
    P [i] = max&gt; i? min(P [i&#39;],max-i):0。如果我们可以扩展到max以上,那么我们通过匹配字符超出max来扩展,相对于c = i的新中心的镜像字符。最后,当我们有不匹配时,我们更新新的max = i + P [i]。
  5. 参考:algorithm description in wiki page

答案 4 :(得分:1)

我使用动画图形制作了关于此算法的视频。希望它能帮助你理解它! https://www.youtube.com/watch?v=kbUiR5YWUpQ

答案 5 :(得分:1)

这些材料对我理解很有帮助: http://solutionleetcode.blogspot.com/2013/07/leetcode-longest-palindromic-substring.html

将T定义为以每个字符为中心的最长回文子串的长度。

关键是,当较小的回文完全嵌入较长的回文内时,T [i]也应该在较长的回文内对称。

否则,我们将不得不从头开始计算T [i],而不是从对称的左侧部分进行计算。

答案 6 :(得分:0)

class Palindrome
{
    private int center;
    private int radius;

    public Palindrome(int center, int radius)
    {
        if (radius < 0 || radius > center)
            throw new Exception("Invalid palindrome.");

        this.center = center;
        this.radius = radius;
    }

    public int GetMirror(int index)
    {
        int i = 2 * center - index;

        if (i < 0)
            return 0;

        return i;
    }

    public int GetCenter()
    {
        return center;
    }

    public int GetLength()
    {
        return 2 * radius;
    }

    public int GetRight()
    {
        return center + radius;
    }

    public int GetLeft()
    {
        return center - radius;
    }

    public void Expand()
    {
        ++radius;
    }

    public bool LargerThan(Palindrome other)
    {
        if (other == null)
            return false;

        return (radius > other.radius);
    }

}

private static string GetFormatted(string original)
{
    if (original == null)
        return null;
    else if (original.Length == 0)
        return "";

    StringBuilder builder = new StringBuilder("#");
    foreach (char c in original)
    {
        builder.Append(c);
        builder.Append('#');
    }

    return builder.ToString();
}

private static string GetUnFormatted(string formatted)
{
    if (formatted == null)
        return null;
    else if (formatted.Length == 0)
        return "";

    StringBuilder builder = new StringBuilder();
    foreach (char c in formatted)
    {
        if (c != '#')
            builder.Append(c);
    }

    return builder.ToString();
}

public static string FindLargestPalindrome(string str)
{
    string formatted = GetFormatted(str);

    if (formatted == null || formatted.Length == 0)
        return formatted;

    int[] radius = new int[formatted.Length];

    try
    {
        Palindrome current = new Palindrome(0, 0);
        for (int i = 0; i < formatted.Length; ++i)
        {
            radius[i] = (current.GetRight() > i) ?
                Math.Min(current.GetRight() - i, radius[current.GetMirror(i)]) : 0;

            current = new Palindrome(i, radius[i]);

            while (current.GetLeft() - 1 >= 0 && current.GetRight() + 1 < formatted.Length &&
                formatted[current.GetLeft() - 1] == formatted[current.GetRight() + 1])
            {
                current.Expand();
                ++radius[i];
            }
        }

        Palindrome largest = new Palindrome(0, 0);
        for (int i = 0; i < radius.Length; ++i)
        {
            current = new Palindrome(i, radius[i]);
            if (current.LargerThan(largest))
                largest = current;
        }

        return GetUnFormatted(formatted.Substring(largest.GetLeft(), largest.GetLength()));
    }
    catch (Exception ex) 
    {
        Console.WriteLine(ex);
    }

    return null;
}

答案 7 :(得分:0)

在字符串中找到最长回文的快速Javascript解决方案:

const lpal = str => {
  let lpal = ""; // to store longest palindrome encountered
  let pal = ""; // to store new palindromes found
  let left; // to iterate through left side indices of the character considered to be center of palindrome
  let right; // to iterate through left side indices of the character considered to be center of palindrome
  let j; // to iterate through all characters and considering each to be center of palindrome
  for (let i=0; i<str.length; i++) { // run through all characters considering them center of palindrome
    pal = str[i]; // initializing current palindrome
    j = i; // setting j as index at the center of palindorme
    left = j-1; // taking left index of j
    right = j+1; // taking right index of j
    while (left >= 0 && right < str.length) { // while left and right indices exist
      if(str[left] === str[right]) { //
        pal = str[left] + pal + str[right];
      } else {
        break;
      }
      left--;
      right++;
    }
    if(pal.length > lpal.length) {
      lpal = pal;
    }
    pal = str[i];
    j = i;
    left = j-1;
    right = j+1;
    if(str[j] === str[right]) {
      pal = pal + str[right];
      right++;
      while (left >= 0 && right < str.length) {
        if(str[left] === str[right]) {
          pal = str[left] + pal + str[right];
        } else {
          break;
        }
        left--;
        right++;
      }
      if(pal.length > lpal.length) {
        lpal = pal;
      }
    }
  }
  return lpal;
}

示例输入

console.log(lpal("gerngehgbrgregbeuhgurhuygbhsbjsrhfesasdfffdsajkjsrngkjbsrjgrsbjvhbvhbvhsbrfhrsbfsuhbvsuhbvhvbksbrkvkjb"));

输出

asdfffdsa

答案 8 :(得分:0)

我经历了同样的挫折/挣扎,我发现此页面https://www.hackerearth.com/practice/algorithms/string-algorithm/manachars-algorithm/tutorial/上的解决方案最容易理解。 我尝试以自己的方式实现此解决方案,并且我想现在可以理解该算法了。我还尝试在代码中填充尽可能多的解释,以解释算法。希望有帮助!

dateadd(month, -1, date_trunc('month', current_date())) as first_day_of_prior_month
dateadd(day, -1, date_trunc('month', current_date())) as last_day_of_prior_month

答案 9 :(得分:-1)

using namespace std;

class Palindrome{
public:
    Palindrome(string st){
        s = st; 
        longest = 0; 
        maxDist = 0;
        //ascii: 126(~) - 32 (space) = 94 
        // for 'a' to 'z': vector<vector<int>> v(26,vector<int>(0)); 
        vector<vector<int>> v(94,vector<int>(0)); //all ascii 
        mDist.clear();
        vPos = v; 
        bDebug = true;
    };

    string s;
    string sPrev;    //previous char
    int longest;     //longest palindrome size
    string sLongest; //longest palindrome found so far
    int maxDist;     //max distance been checked 
    bool bDebug;

    void findLongestPal();
    int checkIfAnchor(int iChar, int &i);
    void checkDist(int iChar, int i);

    //store char positions in s pos[0] : 'a'... pos[25] : 'z' 
    //       0123456
    // i.e. "axzebca" vPos[0][0]=0  (1st. position of 'a'), vPos[0][1]=6 (2nd pos. of 'a'), 
    //                vPos[25][0]=2 (1st. pos. of 'z').  
    vector<vector<int>> vPos;

    //<anchor,distance to check> 
    //   i.e.  abccba  anchor = 3: position of 2nd 'c', dist = 3 
    //   looking if next char has a dist. of 3 from previous one 
    //   i.e.  abcxcba anchor = 4: position of 2nd 'c', dist = 4 
    map<int,int> mDist;
};

//check if current char can be an anchor, if so return next distance to check (3 or 4)
// i.e. "abcdc" 2nd 'c' is anchor for sub-palindrome "cdc" distance = 4 if next char is 'b'
//      "abcdd: 2nd 'd' is anchor for sub-palindrome "dd"  distance = 3 if next char is 'c'
int Palindrome::checkIfAnchor(int iChar, int &i){
    if (bDebug)
          cout<<"checkIfAnchor. i:"<<i<<" iChar:"<<iChar<<endl;
    int n = s.size();
    int iDist = 3;
    int iSize = vPos[iChar].size();
    //if empty or distance to closest same char > 2
    if ( iSize == 0 || vPos[iChar][iSize - 1] < (i - 2)){
        if (bDebug)
              cout<<"       .This cannot be an anchor! i:"<<i<<" : iChar:"<<iChar<<endl; 
        //store char position
        vPos[iChar].push_back(i);
        return -1; 
    }

    //store char position of anchor for case "cdc"
    vPos[iChar].push_back(i);    
    if (vPos[iChar][iSize - 1] == (i - 2))
        iDist = 4;
    //for case "dd" check if there are more repeated chars
    else {
        int iRepeated = 0;
        while ((i+1) < n && s[i+1] == s[i]){
            i++;
            iRepeated++;
            iDist++; 
            //store char position
            vPos[iChar].push_back(i);
        }
    }

    if (bDebug)
          cout<<"       .iDist:"<<iDist<<" i:"<<i<<endl; 

    return iDist;
};

//check distance from previous same char, and update sLongest
void Palindrome::checkDist(int iChar, int i){
    if (bDebug)
            cout<<"CheckDist. i:"<<i<<" iChar:"<<iChar<<endl;
    int iDist;
    int iSize = vPos[iChar].size();
    bool b1stOrLastCharInString; 
    bool bDiffDist;

    //checkAnchor will add this char position 
    if ( iSize == 0){
        if (bDebug)
            cout<<"       .1st time we see this char. Assign it INT_MAX Dist"<<endl; 
        iDist = INT_MAX;
    }
    else {
        iDist = i - vPos[iChar][iSize - 1]; 
    }

    //check for distances being check, update them if found or calculate lengths if not.
    if (mDist.size() == 0) {
        if (bDebug)
             cout<<"       .no distances to check are registered, yet"<<endl;
        return;
    }

    int i2ndMaxDist = 0;
    for(auto it = mDist.begin(); it != mDist.end();){
        if (bDebug)
                cout<<"       .mDist. anchor:"<<it->first<<" . dist:"<<it->second<<endl; 
        b1stOrLastCharInString = false; 
        bDiffDist = it->second == iDist;  //check here, because it can be updated in 1st. if
        if (bDiffDist){
            if (bDebug)
                cout<<"       .Distance checked! :"<<iDist<<endl;
            //check if it's the first char in the string
            if (vPos[iChar][iSize - 1] == 0 || i == (s.size() - 1))
                b1stOrLastCharInString = true;
            //we will continue checking for more...
            else {
                it->second += 2; //update next distance to check
                if (it->second > maxDist) {
                     if (bDebug)
                          cout<<"       .previous MaxDist:"<<maxDist<<endl;
                     maxDist = it->second;
                     if (bDebug)
                          cout<<"       .new MaxDist:"<<maxDist<<endl;
                }
                else if (it->second > i2ndMaxDist) {//check this...hmtest
                     i2ndMaxDist = it->second;
                     if (bDebug)
                          cout<<"       .second MaxDist:"<<i2ndMaxDist<<endl;
                }
                it++; 
            }
        }
        if (!bDiffDist || b1stOrLastCharInString) {
            if (bDebug && it->second != iDist)
                cout<<"       .Dist diff. Anchor:"<<it->first<<" dist:"<<it->second<<" iDist:"<<iDist<<endl;
            else if (bDebug) 
                cout<<"       .Palindrome found at the beggining or end of the string"<<endl;

            //if we find a closest same char.
            if (!b1stOrLastCharInString && it->second > iDist){
                if (iSize > 1) {
                   if (bDebug)
                       cout<<"       . < Dist . looking further..."<<endl; 
                   iSize--;  
                   iDist = i - vPos[iChar][iSize - 1];
                   continue;    
                }
            }
            if (iDist == maxDist) {
                maxDist = 0;
                if (bDebug)
                     cout<<"       .Diff. clearing Max Dist"<<endl;
            }
            else if (iDist == i2ndMaxDist) {
                i2ndMaxDist = 0;
                if (bDebug)
                      cout<<"       .clearing 2nd Max Dist"<<endl; 
            }

            int iStart; 
            int iCurrLength;
            //first char in string
            if ( b1stOrLastCharInString && vPos[iChar].size() > 0 && vPos[iChar][iSize - 1] == 0){
                iStart = 0;
                iCurrLength = i+1;
            }
            //last char in string
            else if (b1stOrLastCharInString && i == (s.size() - 1)){
                iStart = i - it->second; 
                iCurrLength = it->second + 1;
            }
            else {
                iStart = i - it->second + 1; 
                iCurrLength = it->second - 1;  //"xabay" anchor:2nd. 'a'. Dist from 'y' to 'x':4. length 'aba':3
            }

            if (iCurrLength > longest){
                if (bDebug)
                      cout<<"       .previous Longest!:"<<sLongest<<" length:"<<longest<<endl;
                longest = iCurrLength;               
                sLongest = s.substr(iStart, iCurrLength);

                if (bDebug)
                      cout<<"       .new Longest!:"<<sLongest<<" length:"<<longest<<endl;
            }

            if (bDebug)
                  cout<<"       .deleting iterator for anchor:"<<it->first<<" dist:"<<it->second<<endl; 

            mDist.erase(it++);
        }
    }


    //check if we need to get new max distance
    if (maxDist == 0 && mDist.size() > 0){ 
        if (bDebug)
              cout<<"       .new maxDist needed";
        if (i2ndMaxDist > 0) {
            maxDist = i2ndMaxDist;
            if (bDebug)
              cout<<"       .assigned 2nd. max Dist to max Dist"<<endl;
        }
        else {
            for(auto it = mDist.begin(); it != mDist.end(); it++){
                if (it->second > maxDist)
                    maxDist = it->second;
            }
            if (bDebug)
              cout<<"       .new max dist assigned:"<<maxDist<<endl;
        }
    }  
};        

void Palindrome::findLongestPal(){
    int n = s.length(); 
    if (bDebug){
        cout<<"01234567891123456789212345"<<endl<<"abcdefghijklmnopqrstuvwxyz"<<endl<<endl;            
        for (int i = 0; i < n;i++){
            if (i%10 == 0)
                cout<<i/10;
            else
                cout<<i;
        }
        cout<<endl<<s<<endl;
    }
    if (n == 0)
        return;

    //process 1st char
    int j = 0;
    //for 'a' to 'z' : while (j < n && (s[j] < 'a' && s[j] > 'z'))
    while (j < n && (s[j] < ' ' && s[j] > '~'))
        j++;
    if (j > 0){
        s.substr(j);
        n = s.length();
    }
    // for 'a' to 'z' change size of vector from 94 to 26 : int iChar = s[0] - 'a';
    int iChar = s[0] - ' ';
    //store char position
    vPos[iChar].push_back(0);  

    for (int i = 1; i < n; i++){
        if (bDebug)
            cout<<"findLongestPal. i:"<<i<<" "<<s.substr(0,i+1)<<endl; 
        //if max. possible palindrome would be smaller or equal 
        //             than largest palindrome found then exit
        //   (n - i) = string length to check 
        //   maxDist: max distance to check from i 
        int iPossibleLongestSize = maxDist + (2 * (n - i));
        if ( iPossibleLongestSize <= longest){
            if (bDebug)
                cout<<"       .PosSize:"<<iPossibleLongestSize<<" longest found:"<<iPossibleLongestSize<<endl;
            return;
        }

        //for 'a' to 'z' : int iChar = s[i] - 'a';
        int iChar = s[i] - ' ';
        //for 'a' to 'z': if (iChar < 0 || iChar > 25){
        if (iChar < 0 || iChar > 94){
            if (bDebug)
                cout<<"       . i:"<<i<<" iChar:"<<s[i]<<" skipped!"<<endl;
            continue;
        }

        //check distance to previous char, if exist
        checkDist(iChar, i);

        //check if this can be an anchor
        int iDist = checkIfAnchor(iChar,i);
        if (iDist == -1) 
            continue;

        //append distance to check for next char.
        if (bDebug)
                cout<<"       . Adding anchor for i:"<<i<<" dist:"<<iDist<<endl;
        mDist.insert(make_pair(i,iDist));

        //check if this is the only palindrome, at the end
        //i.e. "......baa" or "....baca" 
        if (i == (s.length() - 1) && s.length() > (iDist - 2)){
            //if this is the longest palindrome! 
            if (longest < (iDist - 1)){
                sLongest = s.substr((i - iDist + 2),(iDist - 1));
            }
        }
    }
};

int main(){
    string s;
    cin >> s;

    Palindrome p(s);
    p.findLongestPal();
    cout<<p.sLongest; 
    return 0;
}