Homeβ€Ί DSAβ€Ί Manacher's Algorithm β€” Longest Palindromic Substring in O(n)

Manacher's Algorithm β€” Longest Palindromic Substring in O(n)

Where developers are forged. Β· Structured learning Β· Free forever.
πŸ“ Part of: Strings β†’ Topic 5 of 8
Master Manacher's algorithm to find the longest palindromic substring in O(n) time.
πŸ”₯ Advanced β€” solid DSA foundation required
In this tutorial, you'll learn:
  • Separator insertion converts all palindromes to odd-length β€” canonical technique, not a hack. One unified case beats branching on odd/even in clarity and implementation.
  • P[i] = palindrome radius. Rightmost boundary [c, r] only advances rightward β€” total expansion work O(n). If r never advances, you are not implementing Manacher.
  • Initialise P[i] = min(r-i, P[mirror]) when i < r. Then try to expand. Update c and r if extended beyond old r. These four lines are the core.
✦ Plain-English analogy ✦ Real code with output ✦ Interview questions
⚑ Quick Answer
Finding the longest palindrome in a string naively requires expanding around every center β€” O(nΒ²). Manacher's insight: if you've already found a big palindrome, any palindrome inside it has a mirror on the other side. Use that mirror to skip recomputation. It's the same 'reuse previous work' idea as KMP, but applied to palindrome radii instead of prefix matches.

LeetCode 5 β€” Longest Palindromic Substring β€” is one of the most common interview problems. The O(n^2) expand-around-centre solution passes most judges. Manacher's O(n) solution demonstrates you understand linear string algorithms at a structural level, not just memorised patterns.

Manacher (1975) is not widely taught in undergraduate curricula, which means knowing it distinguishes you. The underlying technique β€” maintaining a rightmost boundary and exploiting mirror symmetry β€” is the exact same structural insight as KMP and Z-algorithm. Once you see this pattern, you recognise it everywhere.

The Even/Odd Problem and the Transformation

Palindromes come in two flavours: odd-length ('aba') and even-length ('abba'). Handling both cases separately doubles code complexity. Manacher's standard trick: insert a separator character between every character (and at boundaries) to convert all palindromes to odd-length.

Original: 'abba' Transformed: '#a#b#b#a#'

Now every palindrome in the original maps to an odd-length palindrome in the transformed string. '#b#b#' is the palindrome for 'bb'. '#a#b#b#a#' is the palindrome for 'abba'. We only need to handle one case.

manacher_transform.py Β· PYTHON
12345
def transform(s: str) -> str:
    return '#' + '#'.join(s) + '#'

print(transform('abba'))   # '#a#b#b#a#'
print(transform('racecar'))# '#r#a#c#e#c#a#r#'
β–Ά Output
#a#b#b#a#
#r#a#c#e#c#a#r#

The P Array β€” Palindrome Radii

For the transformed string T, we compute P[i] = the radius of the longest palindrome centred at i (not counting the centre itself). So P[i] = k means T[i-k..i+k] is a palindrome.

The length of the corresponding original palindrome is P[i] (the separator characters cancel out).

For T = '#a#b#b#a#': - P[0]=0 ('#') - P[1]=1 ('#a#') - P[2]=0 ('a') - P[3]=3 ('#a#b#b#a#') ← full string - P[4]=3 (same, centre is '#' between two b's) - etc.

πŸ”₯
Key PropertyFor any position i in transformed T, the original palindrome length = P[i], and the original string starting position = (i - P[i]) / 2.

Manacher's Algorithm β€” Full Implementation

Manacher maintains a 'mirror window' [l, r] β€” the palindrome with the rightmost boundary seen so far (centre c, right edge r). For each i, if i < r, initialise P[i] from the mirror position (2*c - i). Then try to expand.

manacher.py Β· PYTHON
123456789101112131415161718192021222324252627282930313233
def manacher(s: str) -> str:
    # Transform to handle even/odd uniformly
    T = '#' + '#'.join(s) + '#'
    n = len(T)
    P = [0] * n
    c = r = 0  # centre and right boundary of rightmost palindrome

    for i in range(n):
        mirror = 2 * c - i  # mirror of i around c

        if i < r:
            P[i] = min(r - i, P[mirror])

        # Try to expand around i
        left, right = i - P[i] - 1, i + P[i] + 1
        while left >= 0 and right < n and T[left] == T[right]:
            P[i] += 1
            left -= 1
            right += 1

        # Update rightmost palindrome boundary
        if i + P[i] > r:
            c, r = i, i + P[i]

    # Find maximum in P
    max_len, centre = max((v, i) for i, v in enumerate(P))
    start = (centre - max_len) // 2
    return s[start : start + max_len]

print(manacher('babad'))       # 'bab' or 'aba'
print(manacher('cbbd'))        # 'bb'
print(manacher('racecar'))     # 'racecar'
print(manacher('abacabadabacaba')) # 'abacabadabacaba'
β–Ά Output
bab
bb
racecar
abacabadabacaba

Why it's O(n) β€” The Rightmost Boundary Argument

The r pointer only ever moves right β€” never decreases. Each time we expand (the while loop), r increases. So the total number of expansions across all iterations is at most n. All other operations per iteration are O(1). Total: O(n).

This is the same argument as KMP (pointer only moves forward) and Z-algorithm (r only moves right). Recognising this pattern is key to understanding linear string algorithms.

πŸ”₯
ComplexityO(n) time, O(n) space β€” both for the transformed string and the P array. The transform doubles the length, but that's still O(n).

Finding All Palindromic Substrings

Manacher computes all palindrome radii in one pass, so you can use P to answer queries like 'is s[l..r] a palindrome?' in O(1) after O(n) preprocessing.

all_palindromes.py Β· PYTHON
123456789101112131415161718
def all_palindrome_lengths(s: str) -> list[int]:
    """Return length of longest palindrome centred at each position."""
    T = '#' + '#'.join(s) + '#'
    n = len(T)
    P = [0] * n
    c = r = 0
    for i in range(n):
        if i < r:
            P[i] = min(r - i, P[2*c - i])
        while i-P[i]-1 >= 0 and i+P[i]+1 < n and T[i-P[i]-1] == T[i+P[i]+1]:
            P[i] += 1
        if i + P[i] > r:
            c, r = i, i + P[i]
    # Extract only original positions (every other entry in P)
    return [P[2*i+1] for i in range(len(s))]  # odd centres

print(all_palindrome_lengths('abacaba'))
# [1, 3, 1, 7, 1, 3, 1] β€” longest palindrome at each centre
β–Ά Output
[1, 3, 1, 7, 1, 3, 1]

🎯 Key Takeaways

  • Separator insertion converts all palindromes to odd-length β€” canonical technique, not a hack. One unified case beats branching on odd/even in clarity and implementation.
  • P[i] = palindrome radius. Rightmost boundary [c, r] only advances rightward β€” total expansion work O(n). If r never advances, you are not implementing Manacher.
  • Initialise P[i] = min(r-i, P[mirror]) when i < r. Then try to expand. Update c and r if extended beyond old r. These four lines are the core.
  • The rightward-only pointer argument proves O(n) for KMP, Z, and Manacher. This unifying structure is the real lesson β€” all three are the same insight in different forms.
  • In interviews: O(n^2) expand-around-centre passes most judges. Manacher is the follow-up question that filters strong candidates.

Interview Questions on This Topic

  • QWhy do we insert separator characters in Manacher's algorithm?
  • QWhat does P[i] represent, and how is it used to recover the original palindrome?
  • QExplain why Manacher's algorithm is O(n) and not O(nΒ²).
  • QHow is Manacher's algorithm similar in structure to KMP and the Z-algorithm?

Frequently Asked Questions

Is Manacher's algorithm commonly asked in interviews?

Yes β€” LeetCode problem 5 'Longest Palindromic Substring' is a top interview problem. The O(nΒ²) expand-around-centre solution is acceptable for most interviews, but Manacher's O(n) solution demonstrates strong algorithmic understanding.

What separator character should I use?

Any character guaranteed not to appear in the input. '#' is conventional. For Unicode strings, you can use a character outside the input alphabet. The separator ensures two different separator positions can never form a match.

πŸ”₯
Naren Founder & Author

Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.

← PreviousBoyer-Moore String Search AlgorithmNext β†’Aho-Corasick Multi-Pattern String Matching
Forged with πŸ”₯ at TheCodeForge.io β€” Where Developers Are Forged