Manacher's algorithm
Find the longest palindrome around every center in linear time by reusing mirror images inside a palindrome already found.
O(n)Space O(n)3 practice problemsStep-through pictureThe idea
Expanding around every center finds palindromes in O(n^2) time. Manacher's algorithm gets the same answers in O(n) time. It is named after Glenn Manacher, who published it in 1975.
The trick is a mirror. Suppose you already know a long palindrome. Its left half is the mirror image of its right half. So a palindrome centered on the left side has a twin centered on the right side, at least up to the edge of the big palindrome. You can copy that size for free and only grow past the edge.
To treat odd and even palindromes the same way, the algorithm first puts a # between every two letters and at both ends: abba becomes #a#b#b#a#. Now every palindrome has a single middle character. For each position it stores radius[i]: how far the palindrome around i reaches. That number is also the length of the matching palindrome in the original text.
An everyday example
Reading a symmetric building
You walk along a long building and note how far the symmetry reaches around each window. Once you find a big symmetric front, its left half is a mirror of its right half. When you reach a window on the right half, you don't measure from scratch. You look at its mirror window on the left half, which you already measured, and copy that number. You only walk further when the copied symmetry touches the edge of the big front, because beyond that edge you don't know anything yet.
| In the example | In the code |
|---|---|
| The big symmetric front you know about | The palindrome from 2 * center - right to right |
| The window you stand at | Position i in t |
| Its mirror window on the left half | 2 * center - i |
| Copying the mirror's number, capped at the edge | Math.min(right - i, radius[2 * center - i]) |
| Measuring past the edge | The while loop that grows radius[i] |
From the slow way to manacher's algorithm
- Expanding around every center costs
O(n^2)on text likeaaaaaaaa, because each center compares letters that earlier centers already compared. To get faster, you must never compare letters when the answer is already known. - Here is what a known palindrome already tells you. If the text from
center - rtocenter + ris a palindrome, its right half is the mirror image of its left half. So the palindrome around a positionion the right side looks exactly like the one around its mirror,2 * center - i, as long as both stay inside the big palindrome. - That means
radius[i]can start at the mirror's radius instead of at 0. There's one limit: the copy is only proven up to the big palindrome's edge,right. So start atMath.min(right - i, radius[2 * center - i]), and grow past the edge with ordinary comparisons. - Odd and even palindromes would need two copies of all this, so first put
#between all letters and at both ends. Then every palindrome inthas a single middle character. Walkifrom left to right, copy from the mirror, grow, and whenever a palindrome reaches pastright, make it the newcenterandright. That loop is Manacher's algorithm.
How it works
- Build
tby putting#around every character, soababecomes#a#b#a#. Now every palindrome inthas a single middle. - Make
radius, filled with0.radius[i]will be how many characters the palindrome aroundireaches on each side. - Keep
centerandright: the palindrome found so far that reaches furthest right, and the index where it ends. - For each
i, ifi < right, startradius[i]at its mirror's valueradius[2 * center - i], but no more thanright - i, the room left before the edge. - Grow
radius[i]while the characters just outside it on both sides are equal. - If the palindrome around
inow reaches pastright, it becomes the newcenterandright. - At the end,
radius[i]is the length of the palindrome in the original text centered there. The biggest value is the longest palindrome.
Watch it run
This picture runs the code below on a real input. Press Step to move one step at a time, or Play to watch. The highlighted line is the one that just ran. Change the input to see other cases.
See every step at onceThe same run, laid out side by side
The code
Point at a note to light up the lines it explains.
function manacher(text) {const t = "#" + text.split("").join("#") + "#";const radius = new Array(t.length).fill(0);let center = 0;let right = 0;for (let i = 0; i < t.length; i++) {if (i < right) {radius[i] = Math.min(right - i, radius[2 * center - i]);}while (i - radius[i] - 1 >= 0 &&i + radius[i] + 1 < t.length &&t[i - radius[i] - 1] === t[i + radius[i] + 1]) {radius[i]++;}if (i + radius[i] > right) {center = i;right = i + radius[i];}}return radius;}
How fast it is
| Time | O(n) |
|---|---|
| Extra memory | O(n) |
The edge right only moves right, so all the growing steps together are at most about 2n. The t string and radius array each hold about 2n items.
- Best case
O(n)Text with no palindrome longer than one letter, like
abcdef: almost nothing grows, and each position doesO(1)work. - Average case
O(n)Any text: the total growth is limited by how far
rightcan move. - Worst case
O(n)One letter repeated, like
aaaaaaaa: big palindromes everywhere, but thewhileloop grows only 15 times for 8 letters, because each step pushesrightforward.
Common mistakes
- Copying the mirror's radius without the
right - icap. Past the edge the mirror proves nothing, and the result comes out too big. - Forgetting the
#characters and handling even palindromes separately. It works but doubles the code and the chances of an off-by-one bug. - Looping over
text.lengthwhen reading the result.radiushas2n + 1entries, one per character oft, so scan all oft.lengthor you miss the palindromes on the right. - Converting back to the original index with
i / 2instead of(i - radius[i]) / 2. The first is the middle, not the start.
When to use it
Look for these clues in a problem:
- You need the longest palindrome, or all palindromes, in text that can be
10^5characters or more. - An
O(n^2)expand-around-center solution is too slow for the given size. - You need the palindrome size at every center, not just one answer.
- The problem counts palindromic substrings in very long text.
Check yourself
Answer each one in your head first, then open it.
What is t for noon, and where is the largest radius?
t is #n#o#o#n#, and the largest radius is 4, at the middle #, index 4. That matches noon, which has length 4.
Why is the copied value capped at right - i?
The mirror argument only covers letters inside the known palindrome. Past right, nothing has been compared yet, so the mirror's radius proves nothing there.
Why does a radius in t equal the palindrome's length in the original text?
A longest palindrome in t always starts and ends with an added #. Its 2r + 1 characters are r + 1 added # letters and r real letters.
At most how many times can the while loop grow a radius during the whole run?
About 2n + 1 times, the length of t. Each successful step moves right forward, and right never moves back.
Practice problems
Stuck on one? Tuto is in the playground too, and gives you hints, never the answer.
0 of 3 solved. Solve one to earn the bronze medal, half for silver, and all of them for gold.
Start practicingGo further
You don't need these to start practicing. Open one when you want more.
Why it works
The reasoning that makes it always right
Two rules hold at the top of each loop. First, every radius[j] for j < i is exact: the palindrome around j in t reaches exactly radius[j] steps each way. Second, center and right describe the palindrome found so far whose right end is furthest right. Both hold at the start, with nothing computed and right = 0.
The copy step is safe. Inside the palindrome around center, the text read to the left from i equals the text read to the right from the mirror 2 * center - i, and the other way round. So any palindrome around the mirror that stays inside the big one has a twin around i. Capping at right - i keeps only the part that's proven, and the while loop checks everything past that point with real comparisons, so radius[i] ends exact.
If the copied value is smaller than the room right - i, the very first check fails, because the mirror's palindrome also stopped there. So every successful growth step happens past right, and afterwards right moves to the new end. Since right only moves right and stops at the end of t, all growth steps together are at most t.length, which is 2n + 1. The loop is O(n), and when it ends every radius is exact.
Going deeper
3 more ideas
Why copying the mirror is safe
Inside the palindrome around center, the text at i looks exactly like the text at its mirror 2 * center - i, read backward. So any palindrome around the mirror that stays inside the big palindrome also exists around i. If the mirror's palindrome reaches the edge, only the part inside the edge is guaranteed, which is why the copied value is capped at right - i.
Why it is linear
Copying the mirror costs O(1). The while loop is the only part that can be slow, but every time it grows past right, right moves right afterwards, and right never moves left. It can only move right about 2n times in total, so all the growing together costs O(n). Growth that stays inside right stops at the first check, because the mirror already told us the answer.
Turning radii back into the original text
If radius[i] is the biggest value, the palindrome in the original text starts at (i - radius[i]) / 2 and has length radius[i]. For counting all palindromic substrings, the palindrome around i contains Math.ceil(radius[i] / 2) smaller palindromes with the same middle, so add those up.
Work it by hand
Every step in a table, with why
Find every palindrome radius in a 6-letter text. At index 11 of t, the mirror's radius is too big and must be capped.
text = "abacab", so t = "#a#b#a#c#a#b#"
| Step | i | t[i] | Start from mirror | radius[i] | center, right | What happens | Why |
|---|---|---|---|---|---|---|---|
| 1 | 0 | # | - | 0 | 0, 0 | nothing to grow | Index 0 has no left side, so radius[0] stays 0. |
| 2 | 1 | a | - | 1 | 1, 2 | grow 1: new box | 1 < right is false, so there's no mirror. t[0] and t[2] are both #, so it grows to 1. It reaches index 2, past right = 0, so it becomes the new box. |
| 3 | 2 | # | - | 0 | 1, 2 | no growth | 2 < 2 is false, so no mirror. t[1] is a and t[3] is b. |
| 4 | 3 | b | - | 3 | 3, 6 | grow 3: new box | Three pairs match, covering #a#b#a#, which is aba in the original text. |
| 5 | 4 | # | mirror 2: min(2, 0) = 0 | 0 | 3, 6 | copy 0 | Inside the box. The mirror of 4 around 3 is 2, which had radius 0. One check, b against a, fails. |
| 6 | 5 | a | mirror 1: min(1, 1) = 1 | 1 | 3, 6 | copy 1 | The mirror had radius 1, and there is exactly 1 step of room. The next pair, t[3] and t[7], is b against c, so it stays 1. |
| 7 | 6 | # | - | 0 | 3, 6 | no growth | 6 < 6 is false, so the box doesn't help. a against c fails. |
| 8 | 7 | c | - | 5 | 7, 12 | grow 5: new box | Five pairs match, covering t[2] to t[12], which is bacab in the original text. The new box reaches index 12. |
| 9 | 8 | # | mirror 6: min(4, 0) = 0 | 0 | 7, 12 | copy 0 | The mirror had radius 0. One failed check. |
| 10 | 9 | a | mirror 5: min(3, 1) = 1 | 1 | 7, 12 | copy 1 | The mirror had radius 1 with room to spare, so the answer is 1. One failed check, c against b. |
| 11 | 10 | # | mirror 4: min(2, 0) = 0 | 0 | 7, 12 | copy 0 | One failed check. |
| 12 | 11 | b | mirror 3: min(1, 3) = 1 | 1 | 7, 12 | capped at 1 | The mirror, index 3, had radius 3, but only 1 step of room is left before right = 12. The cap matters: the next step would need t[13], which doesn't exist. |
| 13 | 12 | # | - | 0 | 7, 12 | at the end | 12 < 12 is false, and there's nothing to the right. The loop ends. |
It returns [0, 1, 0, 3, 0, 1, 0, 5, 0, 1, 0, 1, 0]. The largest radius is 5 at i = 7, so the longest palindrome starts at (7 - 5) / 2 = 1 with length 5: bacab. The while loop grew only 9 times in total.
Compared with other ways
When to pick something else
| Approach | Time | Extra memory | Choose it when |
|---|---|---|---|
| Manacher's algorithm | O(n) | O(n) | Long texts, or many palindrome questions about one text: every center's radius in one pass. |
| Expand around the center | O(n^2) | O(1) | Short texts and most interviews: far simpler to write and explain. |
| Hashing with binary search | O(n log n) | O(n) | You already have prefix hashes of the text and its reverse. Binary search each center's radius; simpler to reason about, but hash collisions are possible. |
| Dynamic programming table | O(n^2) | O(n^2) | Small texts where you want a stored answer for every piece. |
Extra memory: O(n): t has 2n + 1 characters and radius has 2n + 1 numbers.
How the steps grow
- Manacher's algorithm
O(n) - Expand around the center, Dynamic programming table
O(n^2) - Hashing with binary search
O(n log n)
| Approach | n = 10 | n = 1,000 | n = 1,000,000 |
|---|---|---|---|
| Manacher's algorithm | 10 | 1,000 | 1 million |
| Expand around the center | 100 | 1 million | 1 trillion |
| Hashing with binary search | 33 | 9,966 | 20 million |
| Dynamic programming table | 100 | 1 million | 1 trillion |
Inputs that need care
5 tricky inputs
- Empty text:
"" tbecomes##, nothing grows, and it returns[0, 0]. The longest palindrome has length 0.- One letter:
"z" tis#z#and it returns[0, 1, 0]: a palindrome of length 1.- All the same letter:
"aaaa" - It returns
[0, 1, 2, 3, 4, 3, 2, 1, 0]. The middle#has radius 4, the whole text, which starts at(4 - 4) / 2 = 0. - Text that contains
#itself:"a#b" - Still correct. The two positions compared always have the same parity, even or odd, so an added
#is only ever compared with another added#. Any separator character works. - A very long text, like 1,000,000 characters
split("")first makes an array of a million strings, andtdoubles the size. To save memory, skiptand read it on the fly:#when the index is even, andtext[(k - 1) / 2]whenkis odd.
Variations and follow-ups
Twists interviewers add
Is any piece a palindrome?
After one run, checking whether text.slice(l, r + 1) is a palindrome takes O(1). Its middle in t is at index l + r + 1, so check that radius[l + r + 1] >= r - l + 1.
Longest palindrome at the start
Look for the largest i where i - radius[i] === 0: that palindrome touches the start of t. Its radius is the length of the longest palindrome that starts at index 0, which gives the shortest text to add in front to make the whole thing a palindrome.
Counting all palindromes
The palindrome around i contains Math.ceil(radius[i] / 2) palindromes of the original text with the same middle. Adding that up over every i counts all palindromic pieces in O(n).
Two arrays instead of #
Some versions skip t and keep one array for odd palindromes and one for even ones. They use less memory but need two nearly identical loops, which is where off-by-one bugs hide.
In an interview
Spot it, explain it, answer follow-ups
- Spot it when a palindrome question has a large limit, like
nup to10^6, whereO(n^2)is too slow. Fornup to a few thousand, expand around the center is enough, and interviewers usually prefer it. - Explain the mirror in one sentence: "inside a known palindrome, the right side repeats the left side backwards, so I copy the mirror's radius up to the edge, then grow". Then explain the
#letters as a way to make every palindrome odd. - Be ready to argue
O(n): every successful growth step movesrightto the right, andrightnever moves back. - Test by hand: one letter,
aaaa, and a case where the cap matters, like index 11 oftforabacab. The usual follow-up is reading the answer back: the start is(i - radius[i]) / 2and the length isradius[i].
Where frontend code uses it
The same idea in real UI code
- You rarely write Manacher's algorithm in UI code, but it shows a pattern you will use: reuse work you already did (here, the mirror) instead of recomputing it. React's memoization and incremental layout follow the same principle.
- Text-analysis features that run in the browser on large pasted documents, such as finding repeated or mirrored sequences, need linear-time scans like this to stay responsive.