Skip to content
Home DSA Manacher's Algorithm — Stale Centre Update Bug

Manacher's Algorithm — Stale Centre Update Bug

Where developers are forged. · Structured learning · Free forever.
📍 Part of: Strings → Topic 5 of 8
Manacher bug: produced 1M-long false palindrome due to stale centre.
🔥 Advanced — solid DSA foundation required
In this tutorial, you'll learn
Manacher bug: produced 1M-long false palindrome due to stale centre.
  • 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
  • Manacher's algorithm finds the longest palindromic substring in O(n) time by exploiting mirror symmetry.
  • Transforms string by inserting separators ('#') to unify even and odd length palindromes.
  • Uses palindrome radius array P and a rightmost boundary (c, r) to skip redundant expansions.
  • Total expansions are O(n) because the right pointer only moves forward.
  • Production insight: common bugs include incorrect mirror calculation when i exceeds r, and off-by-one in start index extraction.
  • Biggest mistake: forgetting that the original palindrome length is just P[i] (not 2*P[i]+1).
🚨 START HERE

Quick Debug: Manacher's Algorithm

Use this cheat sheet when your manacher implementation produces wrong results.
🟡

Longest palindrome is wrong length (usually too short)

Immediate ActionCompute brute force for a small string (length < 10) and compare.
Commands
print('T:', T) and print('P:', P) to verify radius array matches expectations.
Check mirror calculation: mirror = 2*c - i. If i >= r, initialise P[i] = 0, not from mirror.
Fix NowSet P[i] = min(r - i, P[mirror]) only when i < r. Otherwise set to 0 before expansion.
🟡

Algorithm returns palindrome but result is a different valid palindrome

Immediate ActionCheck if there are multiple longest palindromes; the algorithm may return the first or last.
Commands
Print all indices where P[i] == max_len and extract each candidate.
Ensure the loop iterates over all i in range(n) – some implementations stop early.
Fix NowUse max((v, i) for i, v in enumerate(P)) to get the last occurrence, or store the first.
Production Incident

False Longest Palindrome in DNA Sequence Analysis

A bioinformatics pipeline produced an incorrect longest palindrome for a 10M nucleotide string, misidentifying a non-palindromic region as the longest due to a bug in radius update.
SymptomPipeline returned a palindrome of length 1,004,837 for a region that was actually not a palindrome. Validation showed the same region was not symmetric.
AssumptionThe algorithm implementation from a textbook was assumed correct because it passed small test cases.
Root causeThe rightmost boundary update after expansion used if i + P[i] > r but forgot to update centre 'c' when the new palindrome extended beyond r, causing subsequent mirror calculations to use a stale centre.
FixEnsure both c and r are updated together, and re-tested with validation against brute force for random strings up to length 1000.
Key Lesson
Always test linear string algorithms with random strings and validate against O(n^2) for small n.Edge cases with even-length palindromes and strings of repeated characters trigger boundary bugs.The centre must be updated to the new palindrome's centre when r advances.
Production Debug Guide

Common symptoms and fixed actions for production issues

Algorithm returns palindrome of length 0 for even-length palindrome like 'abba'Check that the separator insertion is correct: every character must be separated. Then verify P array indices: for transformed string, original palindrome length = P[i]. The start index = (i - P[i]) // 2.
Result palindrome is correct but offset by one or two charactersExamine the start index calculation: start = (centre - max_len) // 2. Ensure division uses integer division and that centre refers to the position in transformed string.
Infinite loop or crash for very long stringsCheck the while loop expansion condition: left >= 0 and right < n. Ensure left and right are updated correctly after incrementing P[i].

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#
📊 Production Insight
Using a separator like '#' assumes it never appears in the input. In production, you must choose a character outside the input alphabet. For Unicode strings, use a code point that is guaranteed absent.
If the input might contain any character, consider using a sentinel that is not a valid character (e.g., chr(0) if input is Alphanumeric).
This transformation doubles the string length, but it's still O(n) memory.
🎯 Key Takeaway
One separator trick unifies even and odd palindromes.
No need for two separate expansion loops.
Always choose a separator that cannot occur in the input.

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 Property
For any position i in transformed T, the original palindrome length = P[i], and the original string starting position = (i - P[i]) / 2.
📊 Production Insight
A common off-by-one error is to treat P[i] as the full palindrome length including centre. In transformed string, P[i] is the radius: the number of characters to each side. The original palindrome length exactly equals P[i] because separators cancel out.
When extracting the substring, use integer division: start = (i - P[i]) // 2. Note: (i - P[i]) is always even, so floor division works.
🎯 Key Takeaway
P[i] in transformed T equals original palindrome length.
Original start index = (i - P[i]) // 2.
Never add 1 or subtract 1 — trust the maths.

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
1234567891011121314151617181920212223242526272829
def manacher(s: str) -> str:
    T = '#' + '#'.join(s) + '#'
    n = len(T)
    P = [0] * n
    c = r = 0

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

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

        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

        if i + P[i] > r:
            c, r = i, i + P[i]

    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'))
print(manacher('cbbd'))
print(manacher('racecar'))
print(manacher('abacabadabacaba'))
▶ Output
bab
bb
racecar
abacabadabacaba
📊 Production Insight
The min(r - i, P[mirror]) line is the heart of the O(n) guarantee. If P[mirror] is small, we can't rely on symmetry beyond r-i. Forgetting this min leads to O(n^2) fallback.
The while loop runs only when expansion is possible, and each expansion advances r. So total expansions ≤ n.
Common implementation error: forgetting to update both c and r together. If only r is updated, mirror calculations become invalid.
🎯 Key Takeaway
Four lines form the core: mirror calc, min init, expansion while, right boundary update.
Without the min, complexity degrades.
Update c and r together when expanding rightmost boundary.

C++ Implementation of Manacher's Algorithm

For production systems in C++, the same logic applies with careful memory management. The transformed string uses vector<char> for cache locality. The algorithm returns the longest palindromic substring as a std::string.

manacher.cpp · CPP
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

std::string manacher(const std::string& s) {
    std::vector<char> T = {'#'};
    for (char ch : s) {
        T.push_back(ch);
        T.push_back('#');
    }
    int n = T.size();
    std::vector<int> P(n, 0);
    int c = 0, r = 0;

    for (int i = 0; i < n; ++i) {
        int mirror = 2 * c - i;
        if (i < r) {
            P[i] = std::min(r - i, P[mirror]);
        }

        int left = i - P[i] - 1;
        int right = i + P[i] + 1;
        while (left >= 0 && right < n && T[left] == T[right]) {
            ++P[i];
            --left;
            ++right;
        }

        if (i + P[i] > r) {
            c = i;
            r = i + P[i];
        }
    }

    int max_len = 0;
    int centre = 0;
    for (int i = 0; i < n; ++i) {
        if (P[i] > max_len) {
            max_len = P[i];
            centre = i;
        }
    }

    int start = (centre - max_len) / 2;
    return s.substr(start, max_len);
}

int main() {
    std::cout << manacher("babad") << '\n';
    std::cout << manacher("cbbd") << '\n';
    std::cout << manacher("racecar") << '\n';
    std::cout << manacher("abacabadabacaba") << '\n';
    return 0;
}
▶ Output
bab
bb
racecar
abacabadabacaba
📊 Production Insight
In C++, the transformed string as vector<char> improves cache performance versus std::string due to sequential access. Always use size_t for indices to avoid signed/unsigned mismatches in production code. The std::min call is safe as long as r - i is non-negative — guaranteed by the if (i < r) check.
🎯 Key Takeaway
C++ implementation mirrors Python logic but benefits from contiguous memory and explicit memory management. Use vector<char> for the transformed string and size_t for indexing.

Three Case Mirror Analysis: When to Trust the Mirror

The mirror position mirror = 2*c - i gives the palindrome radius z0 = P[mirror] at that point. However, the mirror radius may extend beyond the current right boundary r. Three cases arise:

Case 1: z0 < r - i (mirror palindrome fully inside the rightmost palindrome) P[i] = z0. No expansion needed — the palindrome is guaranteed symmetric inside the larger palindrome.

Case 2: z0 == r - i (mirror palindrome exactly touches the boundary) P[i] = z0 initially, but expansion may extend beyond r. The while loop will try to expand; if successful, the right boundary advances.

Case 3: z0 > r - i (mirror palindrome extends beyond the boundary) P[i] = r - i. The part of the mirror palindrome beyond r is not confirmed symmetric. Expansion from r outward is required.

These three cases are handled elegantly by P[i] = min(r - i, P[mirror]) when i < r.

three_cases_demo.py · PYTHON
12345678
# Example: T = '#a#b#a#b#a#'
# Let c = 5 (centre of '#a#b#a#b#a#'), P[5] = 5, r = 10
# At i = 7, mirror = 2*5 - 7 = 3, z0 = P[3] = 1
# r - i = 3, so min(3, 1) = 1 => Case 1

# At i = 9, mirror = 1, z0 = 1, r-i = 1 => Case 2
# At i = 8, mirror = 2, z0 = 0, r-i = 2 => Case 1
print("See text for explanation")
▶ Output
See text for explanation
🔥Why the min works
The min ensures we never set P[i] larger than what can be safely inferred from the mirror, preventing false positive radii that would break correctness.
📊 Production Insight
Misidentifying these cases is a common bug: if you incorrectly use z0 when z0 > r-i, you may create a palindrome radius that is not validated. The min function is the safety mechanism. In code reviews, ensure the mirror initialisation is always guarded by i < r.
🎯 Key Takeaway
Three-case analysis justifies the min(r - i, P[mirror]) initialisation. Trust the mirror only up to the right boundary.

Visual P‑Array Table for Example String

Consider input s = "ababa". Transformed T = '#a#b#a#b#a#'. The P‑array for each index i:

`` Index i: 0 1 2 3 4 5 6 7 8 9 10 T[i]: # a # b # a # b # a # P[i]: 0 1 0 3 0 5 0 3 0 1 0 ` - P[3]=3: palindrome #a#b#a# corresponds to original "aba" (length 3). - P[5]=5: palindrome #a#b#a#b#a# corresponds to original "ababa" (length 5). - All even indices (separators) have even radii multiples? Actually they have radii 0,3,0,3,0? Let's correct: The above table is illustrative. For ababa`, the P array values at even indices are: 0,3,0,3,0? Wait recalc: T: # a # b # a # b # a #. Centres at characters: i=1 (a) radius1, i=3 (b) radius3, i=5 (a) radius5, i=7 (b) radius3, i=9 (a) radius1. Centres at separators: i=0 radius0, i=2 radius? T[2-1]..T[2+1] = #a# -> palindrome? Actually check: index2 is '#', left=1 (a), right=3 (b) -> a!=b so radius0. Similarly i=4 radius0? i=4 is '#', left=3 (b), right=5 (a) -> b!=a so radius0. i=6 '#', left=5 (a), right=7 (b) -> a!=b radius0. i=8 '#', left=7 (b), right=9 (a) -> b!=a radius0. i=10 radius0. So P[i] = [0,1,0,3,0,5,0,3,0,1,0]. That's the correct array.

The longest palindrome ababa (length 5) corresponds to centre i=5 with P[5]=5. Start index = (5-5)//2 = 0.

📊 Production Insight
Visualising the P‑array helps quickly spot off-by-ones: if the maximum appears at an even index, it's likely a bug (even indices should have radii only if the original palindrome is even-length, which is fine, but for odd-length strings the max should be at odd index).
🎯 Key Takeaway
P‑array shows radii at each centre. For odd-length strings, max radius occurs at a character centre (odd index in T).
P‑array visualization for T = "#a#b#a#b#a#"
0
1
0
3
0
5
0
3
0
1
0
Maximum radius 5 at centre i=5 (character 'a')

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.

🔥Complexity
O(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).
📊 Production Insight
The O(n) bound relies on the invariant that r never decreases. In production, ensure your code doesn't accidentally reset r (e.g., inside a nested loop). Benchmarking against O(n^2) for n=10^5 should show ~10x speedup.
If you observe O(n^2) performance, check that the while loop is not being executed redundantly — the min guard should prevent expansion when mirror already covers the boundary.
🎯 Key Takeaway
r only moves right, so total expansion work ≤ n.
Same proof as KMP and Z-algorithm.
If it's not O(n), you're not using the mirror correctly.

Advantages and Disadvantages vs Expand-Around-Center

Expand-around-centre (EAC) is the O(n²) baseline: for each of ~2n centres, expand until mismatch. Manacher improves on it by reusing previous expansions.

Advantages of Manacher over EAC: - O(n) vs O(n²) time — crucial for large strings (>10⁴ characters). - Single pass produces all palindrome radii — can be reused for subqueries. - Elegant theoretical demonstration of linear string algorithms.

Disadvantages: - Higher constant factor: EAC is simpler and may be faster for small strings (<1000 characters). - Memory: O(n) extra space for P array (vs O(1) for EAC). - Complexity: harder to implement correctly; bugs in mirror logic are common. - Input must be transformed, adding overhead.

When to use Manacher: - When string length exceeds 10⁵ and you need optimal time. - When you need to answer multiple palindrome queries on the same string (e.g., count all palindromes).

When to use EAC: - Quick prototyping or interviews where O(n²) is acceptable. - When space is constrained. - When the string is very short.

⚠ Performance Trap
For n < 10⁴, EAC may be equally fast due to cache effects and simpler control flow. Always profile before committing to Manacher.
📊 Production Insight
In production, a hybrid approach can be useful: use EAC for small strings and fall back to Manacher for large ones. Alternatively, if memory is tight, EAC's O(1) space is valuable. For long-running servers processing many strings of varying lengths, EAC’s simplicity reduces maintenance burden.
🎯 Key Takeaway
Manacher is optimal asymptotically but has higher overhead. Use EAC for short strings or quick implementations; Manacher for large-scale palindrome processing.

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]
▶ Output
[1, 3, 1, 7, 1, 3, 1]
📊 Production Insight
Using the P array, you can answer queries like 'Is substring from l to r a palindrome?' in O(1) by checking if the palindrome radius at the centre of that substring (in transformed space) covers at least half the length. This is useful for palindrome-related problems in DNA or text analysis.
The all_palindrome_lengths function returns lengths for odd-length centres only. For even-length centres, you need to adjust indexing (use P[2*i] for even centres).
🎯 Key Takeaway
P array enables O(1) palindrome queries after O(n) precomputation.
Extract odd centres every other index.
Even centres require P[2*i] for original even-length palindromes.

Edge Cases and Common Pitfalls

Handling edge cases in Manacher is essential for production reliability. Here are the typical failures:

  1. Empty string: Should return empty string. Our function works because transform('') = '#', P[0]=0, max_len=0, start=0, s[0:0] = ''.
  2. Single character: transform('a') = '#a#', P[1]=1, returns 'a'. Works.
  3. All identical characters: Like 'aaaa'. The algorithm must handle large radii. The while loop may expand many times but total O(n). Ensure recursion depth is not an issue (iterative is fine).
  4. Very long string with no palindrome longer than 1: The algorithm will still run O(n) but each expansion step may be short. No issue.
  5. Characters that could be the separator: If input contains '#', you must choose another separator or escape. The safest approach is to use a character that is not present, e.g., chr(0) for ASCII strings.
manacher_edge_cases.py · PYTHON
1234567891011
def test_manacher_edges():
    from manacher import manacher
    assert manacher('') == ''
    assert manacher('a') == 'a'
    assert manacher('aa') == 'aa'
    assert manacher('ab') in ('a', 'b')
    assert manacher('aaaa') == 'aaaa'
    assert manacher('racecar') == 'racecar'
    print('All edge cases passed')

test_manacher_edges()
▶ Output
All edge cases passed
⚠ Separator Collision
If your input can contain the separator character, the transform will create false positive palindrome expansions. Use an impossible character or dynamically choose one not in the input set.
📊 Production Insight
In production systems processing user-generated input (e.g., social media text), the separator must be chosen carefully. For example, if '#' is allowed, use a Unicode control character like U+0000 (null) or a character outside the Basic Multilingual Plane. Or preprocess to escape the separator.
The edge case of all identical characters can stress test the algorithm: while loops may expand many times, but the O(n) guarantee holds. However, in interpreted languages like Python, deep recursion doesn't apply, but the loop may be slow for n=10^7. Consider using a lower-level language for extremely long strings.
🎯 Key Takeaway
Test empty, single char, all same, no palindrome.
Separator must be guaranteed absent in input.
Profiling may reveal performance issues only at very large n.

Practice Problems

  1. [LeetCode 5 – Longest Palindromic Substring](https://leetcode.com/problems/longest-palindromic-substring/) – Classic problem; implement Manacher and compare with expand-around-centre.
  2. [LeetCode 647 – Palindromic Substrings](https://leetcode.com/problems/palindromic-substrings/) – Count all palindromic substrings; Manacher gives O(n) solution.
  3. [LeetCode 131 – Palindrome Partitioning](https://leetcode.com/problems/palindrome-partitioning/) – Partition string into palindromic substrings; use Manacher to precompute palindrome status for O(n²) DP.
  4. [Codeforces 245H – Queries for Number of Palindromes](https://codeforces.com/problemset/problem/245/H) – Many queries on substring palindromicity; precompute P array.
  5. [Codeforces 159D – Palindrome pairs](https://codeforces.com/problemset/problem/159/D) – Count pairs of palindromic substrings; Manacher helps compute palindrome endpoints.
  6. [SPOJ PALIN – The Next Palindrome](https://www.spoj.com/problems/PALIN/) – Not directly Manacher, but good practice for palindrome logic.

_Tip: For problems requiring multiple queries, precompute palindrome radii with Manacher and reuse the P array._

📊 Production Insight
When solving palindrome problems in production (e.g., censoring or pattern recognition), Manacher's O(n) precomputation allows O(1) queries. This is especially valuable when the string is static but queried many times.
🎯 Key Takeaway
Practice with these problems solidifies understanding. Manacher's real power is in answering many palindrome queries on one string.
🗂 Comparison: String Algorithms for Palindrome Search
Time and space complexity for longest palindromic substring
AlgorithmTimeSpaceUse Case
Brute ForceO(n^3)O(1)Teaching only
Expand Around CenterO(n^2)O(1)Easy to code, good for small strings
ManacherO(n)O(n)Optimal, but more complex
DP (Table)O(n^2)O(n^2)If you need all palindrome substrings, not just longest

🎯 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.
  • Always test edge cases: empty, single char, all same, no palindrome. Validate against brute force for random small strings.

⚠ Common Mistakes to Avoid

    Forgetting to update the centre c when r advances
    Symptom

    Subsequent mirror calculations use a stale centre, causing wrong P[i] values and potentially incorrect palindrome start.

    Fix

    Update both c = i and r = i + P[i] together when i + P[i] > r.

    Incorrect initialisation of P[i] when i >= r
    Symptom

    P[i] may be set to P[mirror] even when i is outside the rightmost palindrome, leading to oversized P[i] and expansions that break correctness.

    Fix

    Only use P[mirror] when i < r. Otherwise set P[i] = 0 before expansion loop.

    Off-by-one in start index calculation
    Symptom

    Returned substring is shifted left or right by one or two characters.

    Fix

    Use integer division: start = (centre - max_len) // 2. Ensure centre is the index in transformed string where max_len occurs.

Interview Questions on This Topic

  • QWhy do we insert separator characters in Manacher's algorithm?Mid-levelReveal
    To convert all palindromes to odd-length centered on either a character or a separator, so we only need to handle one case. This avoids branching for even-length palindromes and simplifies radius computation.
  • QWhat does P[i] represent, and how is it used to recover the original palindrome?Mid-levelReveal
    For the transformed string T, P[i] is the radius of the palindrome centered at i (number of characters on each side). The original palindrome length equals P[i], and the starting index in the original string is (i - P[i]) // 2.
  • QExplain why Manacher's algorithm is O(n) and not O(n²).SeniorReveal
    The rightmost boundary r only moves right. Each successful expansion in the while loop advances r. Since r is bounded by n, total expansions across all iterations is O(n). All other operations per iteration are O(1), giving linear total time.
  • QHow is Manacher's algorithm structurally similar to KMP and the Z-algorithm?SeniorReveal
    All three use a rightmost boundary that advances monotonically and reuse previously computed information (prefix function in KMP, Z-array, or palindrome radii in Manacher) to skip redundant comparisons. This 'never go back' pattern is the key to linear complexity.
  • QHow would you modify Manacher to return all palindromic substrings?SeniorReveal
    After computing P array, iterate over each centre in transformed string. For each P[i] value, you can extract all palindromes: for length from 1 to P[i], the original substring starting at (i - len) // 2 with length len is a palindrome. Collect them, possibly deduplicate using a set of (start, end) pairs.

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.

Can Manacher handle very large strings (e.g., 10^7 characters)?

Yes. O(n) time and O(n) space. The transformed string is 2n+1 characters, which is still O(n). However, memory for the P array and transformed string may be ~100-200 MB for n=10^7. Ensure enough heap space in languages like Java or allocate memory carefully in C++.

How do I handle strings that contain the separator character?

Choose a different separator that is not present. Optionally, you can preprocess the string to replace the separator with an escape sequence, or use an array of integers instead of characters.

🔥
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