Manacher's algorithm finds the longest palindromic substring in O(n) time. The brute-force "expand around every center" approach is O(n²); Manacher keeps that same expand-around-center idea but reuses already-computed radii through a mirror trick, so no character is examined more than a constant number of times.
Core idea. Transform the string by inserting # between every character (and wrapping it in
sentinels), which makes every palindrome odd-length. Then sweep left to right tracking the rightmost
palindrome found so far as a center C and right boundary R. For each new center i inside R,
seed its radius from its mirror across C instead of starting from zero, then only expand past what
the mirror guaranteed.
For s = "aba" the transform is t = ^#a#b#a#$. The radius array ends up p = [0, 0, 1, 0, 3, 0, 1, 0, 0]; the maximum 3 sits at the center b, telling us the longest palindrome is "aba" with length 3.
Intuition
Expanding around every center is the obvious O(n²) method: pick a center, push outward while characters match. Two things make it slow. First, palindromes can be even-length ("abba"), which needs a separate "center between two characters" case. Second, neighboring centers redo each other's work.
The # transform kills the even/odd split: with separators, every palindrome of t is centered on a single character and is odd-length, so one radius array handles both. The mirror trick kills the redundant work: if center i lies inside the current rightmost palindrome (C, R), then by symmetry it looks just like its mirror 2*C - i — at least up to the boundary R. So we can copy p[mirror], clamped by how much room is left (R - i), and only try to expand beyond that. Each expansion either fails immediately or pushes R further right, and R only moves forward, so the total expansion work across the whole sweep is O(n).
Walk through it
Step through the animation on the right. The top row is t = ^#a#b#a#$. Pointer i is the center we are growing; mirror is its reflection across C; C and R (below the row) mark the rightmost palindrome so far. The live p array updates each step.
When i = 2 (the first a), it is outside R, so we start fresh and expand: #a# matches, giving p[2] = 1, and R jumps to 3. When i = 4 (the b), it expands the most — matching #a#b#a# outward to radius 3 — so C, R slide to 4, 7. Now watch i = 6 (the second a): it lies inside R = 7, so its mirror = 2 already proved a radius of 1. We seed p[6] = min(R - i, p[mirror]) = min(1, 1) = 1 for free and only attempt one more expansion (which fails at the sentinel). The biggest radius is p[4] = 3, so the answer is the length-3 palindrome "aba".
The code, line by line
def manacher(s):
t = "^#" + "#".join(s) + "#$"
p = [0] * len(t)
c = r = 0
for i in range(1, len(t) - 1):
if i < r:
mirror = 2 * c - i
p[i] = min(r - i, p[mirror])
else:
p[i] = 0
while t[i - p[i] - 1] == t[i + p[i] + 1]:
p[i] += 1
if i + p[i] > r:
c, r = i, i + p[i]
return max(p)- Line 2 builds
twith#between characters and sentinels^/$on the ends. The sentinels are distinct from everything else, so thewhileexpansion stops at them automatically — no index bounds checks needed. p[i]is the radius of the palindrome centered att[i]; in the transformed string this radius also equals the length of the original palindrome.- Lines 6–8 are the mirror trick: when
iis inside the rightmost palindrome, copy the mirror's radius but clamp it tor - iso it never claims more thanRactually guarantees. - Line 10 covers
iat or beyondR— no mirror to lean on, so start at0. - Lines 11–12 expand: while the characters one step past each end match, grow the radius. The seed means we rarely start from
0, which is what saves the time. - Lines 13–14 push
CandRforward whenever the new palindrome reaches further right than any before — this is the only wayRmoves, and it only ever increases. - Line 15 returns the largest radius; map
iback to recover the substring itself.
Complexity
| Case | Time | Notes |
|---|---|---|
| Time | O(n) (moderate) | R only moves forward; total expansion work is bounded by n |
| Space | O(n) (moderate) | the transformed string t and radius array p are each ~2n |
O(n) (moderate)The while loop looks like it could make the whole thing quadratic, but every successful expansion advances R, and R spans 2n + 1 positions and never retreats. So across the entire outer loop the expansions do at most O(n) total work, and the sweep itself is O(n) — combined, O(n).
When to use / pitfalls
Reach for Manacher when a problem asks for the longest palindromic substring (or to count all
palindromic substrings) and the input is large enough that O(n²) expand-around-center will time out.
For small inputs, plain expand-around-center is simpler and usually fine — interviewers often accept it.
Manacher is the "I know the optimal" answer; be ready to explain the # transform and the mirror trick
rather than memorizing the code.
The most common bug is forgetting the sentinels and then needing fiddly bounds checks in the expansion
loop. With distinct ^ and $ ends, t[i - p - 1] == t[i + p + 1] can never wander off the array,
because the sentinels never match. The other trap is the clamp: seed with min(r - i, p[mirror]), not
just p[mirror] — the mirror's palindrome may extend past R, which Manacher has not yet verified on
this side.
Practice
For t = ^#a#b#a#$, when i = 6 (the second a) and the rightmost palindrome is C = 4, R = 7, what value does Manacher seed p[6] with before expanding?
1. Why insert # between every character of the string?
2. When i is inside the rightmost palindrome (i < R), how is p[i] seeded?
3. What makes Manacher O(n) despite the inner while loop?
4. What is the purpose of the sentinels ^ and $ at the ends of t?