Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Coding Interview Patterns  Module 28 – String patterns

Longest palindromic substring

Find the longest palindromic substring by expanding around its 2n − 1 centres, count every palindrome, and see when the linear Manacher method pays.

  • Intermediate
  • 30 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, quickjs 0.32.0, Java 25, Clang 21.0.0 and Go 1.27.1
  • By MySmartCoPilot
Free core, Pro depth: 10 of 30 minutes free
Free: The problem · The brute force: check every substring · The key idea: expand around every centre · 2 more sections. With a pass: 3 in-depth parts and 3 interview answers, about 20 minutes.

What you will learn

  • Find the longest palindromic substring by expanding around all 2n − 1 centres
  • Prove that expanding each centre until its ends differ finds every palindrome
  • Count every palindromic substring and keep the total in a 64-bit integer
  • Explain how Manacher's algorithm reuses mirror positions to run in linear time
  • Choose between centre expansion and Manacher's algorithm from the size and shape of the input

Before you start

What you’ll learn · Before you start

What you will learn

  • Find the longest palindromic substring by expanding around all 2n − 1 centres
  • Prove that expanding each centre until its ends differ finds every palindrome
  • Count every palindromic substring and keep the total in a 64-bit integer
  • Explain how Manacher's algorithm reuses mirror positions to run in linear time
  • Choose between centre expansion and Manacher's algorithm from the size and shape of the input

Before you start

On this page

The longest palindromic substring is the longest unbroken stretch of a string that reads the same backwards, such as TAGGAT inside TTAGGATC. Every palindrome is centred on a letter, when its length is odd, or on the gap between two letters, when it is even, so a string of n letters has 2n − 1 centres. Widening a window from each centre while its two ends match finds the answer in O(n²) time and O(1) extra memory, against O(n³) for checking every substring. That is fast enough for about 10⁴ letters, and the same expansions count every palindromic substring. Manacher’s algorithm reuses what earlier centres proved and runs in O(n) time.

The problem

The problem

A lab simulates DNA-like strands written with the letters A, C, G and T. A stretch of a strand that reads the same backwards, letter by letter, is a mirrored segment. Biologists use the word palindrome for a different idea, a stretch equal to its reverse complement; here it means only reading backwards. Write longest_palindrome_span(s), which returns (start, length) of the longest mirrored segment, the one that starts first when several are equally long, and (0, 0) for an empty strand. Limits: 0 ≤ n ≤ 10⁴.

Four examples:

  1. "ACGTGCAT" gives (0, 7): ACGTGCA, of odd length, centred on the T at position 3.
  2. "TTAGGATC" gives (1, 6): TAGGAT, of even length, centred between the two Gs.
  3. "AGACTCT" gives (0, 3): AGA, CTC and TCT all have length 3, and AGA starts first.
  4. "ACGT" gives (0, 1): no letter repeats, so every palindrome is a single letter, and the first one wins.

The brute force: check every substring

Check every substring from both ends and keep the longest palindrome. A strand of n letters has n(n + 1)/2 substrings and each check makes up to n/2 comparisons, so the method is O(n³). One letter repeated is its worst case, because then every check succeeds and runs to the middle. The run counts the comparisons:

Every substring checked, with the comparisons counted Python · brute/every_substring.py
# Every substring of a strand, checked from both ends, with the character comparisons counted.

def is_palindrome(s, i, j, counter):
    """Whether s[i..j] reads the same backwards; adds each comparison it makes to counter[0]."""
    while i < j:
        counter[0] += 1
        if s[i] != s[j]:
            return False
        i += 1
        j -= 1
    return True


def longest_by_brute_force(s):
    """(start, length) of the leftmost longest palindromic substring, and the comparisons made."""
    counter = [0]
    best = (0, min(len(s), 1))
    for i in range(len(s)):
        for j in range(i, len(s)):
            if is_palindrome(s, i, j, counter) and j - i + 1 > best[1]:
                best = (i, j - i + 1)
    return best, counter[0]


for strand in ["ACGTGCAT", "TTAGGATC", "AGACTCT", "ACGT"]:
    (start, length), work = longest_by_brute_force(strand)
    print(f"{strand:9} start {start}, length {length}: {strand[start:start + length]:7} {work} comparisons")

# One repeated letter is the worst case: every check succeeds and runs to the middle.
print()
for n in (50, 100, 200):
    _, work = longest_by_brute_force("A" * n)
    print(f"{n} copies of A: {work:,} comparisons")

Output

ACGTGCAT  start 0, length 7: ACGTGCA 32 comparisons
TTAGGATC  start 1, length 6: TAGGAT  32 comparisons
AGACTCT   start 0, length 3: AGA     21 comparisons
ACGT      start 0, length 1: A       6 comparisons

50 copies of A: 10,725 comparisons
100 copies of A: 84,575 comparisons
200 copies of A: 671,650 comparisons

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 every_substring.py

Doubling n multiplies the work by about 8, as a cubic cost does: 84,575 comparisons for 100 letters and 671,650 for 200. The same count for 10⁴ letters is 83,345,832,500, and at the rate the constraints lesson gives for C++, about 10⁸ simple steps a second, that is about 14 minutes.

The key idea: expand around every centre

Instead of guessing both ends of a palindrome, fix its centre. Start with the single letter, for an odd centre, or the two letters beside a gap, for an even one, and widen the window one letter on each side while the two new end letters match. The first mismatch ends that centre: every longer window around the same centre contains the two mismatched letters at mirrored positions, so none of them can be a palindrome. Each centre therefore finds the longest palindrome around it, and the longest of those 2n − 1 answers is the result.

The strand TTAGGATC, numbered 0 to 7, with TAGGAT marked; expanding from the gap between the two Gs finds it.TTAGGATC, 15 centresFrom the gap 3 | 4T0T1A2G3G4A5T6C73 and 4: G = G, length 22 and 5: A = A, length 41 and 6: T = T, length 60 and 7: T ≠ C, stopFound TAGGAT: start 1, length 6

One of 15 centres, expanded until its ends differ

Text description of the diagram

The diagram has two rows, one under the other.

  1. The strand TTAGGATC is drawn as eight cells, each holding a letter and its position from 0 to 7: T, T, A, G, G, A, T and C. A strand of 8 letters has 8 letter centres and 7 gap centres, 15 in all. The six cells from position 1 to 6, TAGGAT, have a double border: they are the longest palindrome.
  2. The steps of one expansion are listed in order, starting from the gap between positions 3 and 4. Positions 3 and 4 hold G and G, which match, so the palindrome has length 2. Positions 2 and 5 hold A and A, length 4. Positions 1 and 6 hold T and T, length 6. Positions 0 and 7 hold T and C, which differ, so the expansion stops. The palindrome found is TAGGAT, which starts at position 1 and has length 6.

The cost depends on how far the windows grow. On one repeated letter they grow to the ends of the strand, and 10⁴ letters cost 50,005,000 comparisons, which C++ makes in about half a second. On a random strand of A, C, G and T, a window grows past the next letter only one time in four, so the work is close to linear. The memory is two indices.

Two more uses come from the same loop. Counting every palindromic substring takes the same expansions: each step that matches adds exactly one palindrome, centred where it was found. Manacher’s algorithm finds the same palindromes in O(n) time by starting each centre from what a mirror position already proved.

A dry run on TTAGGATC

The centres run from left to right, and the best span changes only when a strictly longer palindrome appears, so the first of equally long palindromes is kept.

Centre Letters compared Palindrome Best so far
Letter 0 none: no letter on its left T (0, 1)
Gap 0–1 T and T match, then no letter is left TT (0, 2)
Letter 1 T and A differ T (0, 2)
Gaps 1–2 and 2–3, letters 2 and 3 the first two letters compared differ A, G or none (0, 2)
Gap 3–4 G = G, A = A and T = T, then T and C differ TAGGAT (1, 6)
Letters 4 to 7, gaps 4–5 to 6–7 the first two letters differ, or no letter is on the right one letter or none (1, 6)

The answer is (1, 6). Only one of the 15 centres compared more than one pair of letters.

Exercise

Exercise · Medium · Python

Where the longest mirrored stretch starts, and how long it is

Write longest_palindrome_span(s). It returns a pair (start, length) for the longest substring of s that reads the same backwards, so that s[start:start + length] is that substring. When several are equally long, return the one that starts first. An empty string gives (0, 0).

longest_palindrome_span("ACGTGCAT")  # (0, 7): ACGTGCA
longest_palindrome_span("TTAGGATC")  # (1, 6): TAGGAT
longest_palindrome_span("AGACTCT")   # (0, 3): AGA, the first of three of length 3

Expand around every centre: each letter, and each gap between two neighbours. One sample test has 3,000 letters, so checking every substring will run out of time. Limits: 0 ≤ n ≤ 10⁴, letters A, C, G and T.

Starter code · span.py

def longest_palindrome_span(s):
    """(start, length) of the leftmost longest palindromic substring of s; (0, 0) for ""."""
    # Replace this line with your code.
    return (0, 0)
The sample tests · test_span.py
import random

from span import longest_palindrome_span


def every_substring(s):
    best = (0, min(len(s), 1))
    for i in range(len(s)):
        for j in range(i + 1, len(s) + 1):
            if s[i:j] == s[i:j][::-1] and j - i > best[1]:
                best = (i, j - i)
    return best


def test_examples():
    """the three examples of the prompt"""
    assert longest_palindrome_span("ACGTGCAT") == (0, 7)
    assert longest_palindrome_span("TTAGGATC") == (1, 6)
    assert longest_palindrome_span("AGACTCT") == (0, 3)


def test_short_strings():
    """an empty string, one letter, two letters"""
    assert longest_palindrome_span("") == (0, 0)
    assert longest_palindrome_span("G") == (0, 1)
    assert longest_palindrome_span("GG") == (0, 2)
    assert longest_palindrome_span("GC") == (0, 1)


def test_no_letter_repeats_next_to_itself():
    """no palindrome longer than one letter: the first letter"""
    assert longest_palindrome_span("ACGT") == (0, 1)


def test_whole_strand_and_last_letters():
    """the palindrome may be the whole strand, or reach its last letter"""
    assert longest_palindrome_span("ACCA") == (0, 4)
    assert longest_palindrome_span("GTACCA") == (2, 4)


def test_random_strands_against_every_substring():
    """1,500 random strands, checked against a search of every substring"""
    rng = random.Random(29)
    for _ in range(1500):
        s = "".join(rng.choice("ACGT"[: rng.randint(1, 4)]) for _ in range(rng.randint(0, 14)))
        assert longest_palindrome_span(s) == every_substring(s), f"longest_palindrome_span({s!r})"


def test_three_thousand_letters():
    """n = 3,000: fast enough only without checking every substring"""
    rng = random.Random(41)
    s = "".join(rng.choice("ACGT") for _ in range(3000))
    s = s[:1200] + "GATTACAACATTAG" + s[1214:]
    start, length = longest_palindrome_span(s)
    assert length >= 14
    assert s[start:start + length] == s[start:start + length][::-1]
A hint

There are 2n − 1 centres: number them from 0 to 2n − 2, so that centre c starts with lo = c // 2 and hi = lo + c % 2. Widen while both ends are inside the string and match. When the loop stops, which positions are the first and the last letter of the palindrome it found?

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Reverse Text, Mirror & Upside Down Reverse a strand to see at a glance which stretches read the same backwards.

Check yourself

3 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 3 How many centres does centre expansion try on a strand of 6 letters?

    Type a number.

    Show the answer to question 1

    Answer: 11

    Every letter is the centre of the palindromes of odd length around it, 6 centres, and every gap between two neighbours is the centre of the even ones, 5 more: 2n − 1 = 11 in all.

  2. Question 2 of 3 A centre is being widened and s[lo] differs from s[hi]. Why can no longer window around the same centre be a palindrome?

    Choose one answer.

    Show the answer to question 2

    Answer: Every longer window contains s[lo] and s[hi] at mirrored positions

    A window around the same centre that reaches further still has s[lo] and s[hi] at the same distance from its centre, one on each side. A palindrome needs those two letters to be equal, and they are not, so every longer window fails too.

  3. Question 3 of 3 The loop replaces the best span whenever length >= best_len. What does it return for "AGACTCT"?

    Choose one answer.

    Show the answer to question 3

    Answer: (4, 3), for TCT

    AGA, CTC and TCT all have length 3, and with >= every one of them replaces the one before. TCT has the last centre of the three, so it is what remains; the problem asks for AGA, which only > keeps.

Centre expansion proved, in five languages, and counted Pro: opens with a Pro, Premium or Ultimate pass

Centre expansion is short, and it is correct because of two facts about centres. Here both are proved, the method runs in five languages with recorded output, and the same loop counts every palindrome.

In this part:

  • Why every palindrome is found, proved with an invariant
  • The code in five languages
  • Counting every palindromic substring in 64 bits

3 runnable examples (C++, Go, Java, JavaScript, Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Manacher's algorithm: linear time, with counted comparisons Pro: opens with a Pro, Premium or Ultimate pass

Centre expansion repeats work on strands that repeat themselves. Manacher’s algorithm keeps what earlier centres proved, and it runs here against centre expansion on 100,000 letters, with every comparison counted.

In this part:

  • The mirror rule inside the rightmost palindrome
  • Why it makes at most about 4n comparisons
  • Measured: 100,000 letters three ways
  • When it is worth writing

1 runnable example (Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Four bugs reproduced, the variants and a practice set Pro: opens with a Pro, Premium or Ultimate pass

Four one-character slips account for most wrong versions of centre expansion. The drill below hunts each of them down on short strands; the variants and a practice set come after it.

In this part:

  • Four plausible bugs and the shortest strands that break them
  • Variants that look the same and are not
  • A practice set with sample answers

1 runnable example (Python) · about 2 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Interview questions

  1. Fresher to mid · Warm-up

    Why does centre expansion try 2n − 1 centres on a string of n letters, and what goes wrong with only the n letters?

    Every palindrome has a centre. One of odd length, such as ACGTGCA, is centred on a letter, the T in the middle; one of even length, such as TAGGAT, is centred on the gap between its two middle letters, the two Gs. A string of n letters has n letters and n − 1 gaps between neighbours, so 2n − 1 centres in all. Expanding only from the n letters finds every odd palindrome and misses every even one: on "AA" it reports a single letter instead of AA, and on TTAGGATC it finds nothing longer than one letter, missing both TT and TAGGAT. Numbering the centres 0 to 2n − 2, with lo = c // 2 and hi = lo + c % 2, handles both kinds in one loop.

  2. Mid to senior

    A teammate finds the longest palindromic substring of a 50,000-character log line with an n × n table of booleans, is_pal[i][j], filled by dynamic programming, and the job runs out of memory. Estimate what the table needs, and say what you would use instead and why.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Fresher to mid

    A Java method counts the palindromic substrings of a status log with centre expansion and an int counter. On a log of 120,000 identical heartbeat characters it takes minutes and then prints a negative number. Explain both with numbers, and fix the method.

    Model answer, Pro. First step and follow-ups

  4. Senior to staff

    Your centre-expansion solution for the longest palindromic substring passes every sample test but times out on a hidden test with n = 200,000. What did that test probably contain, how do you confirm it without seeing it, and what do you change?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A string of n letters has 2n − 1 centres: n letters for odd palindromes and n − 1 gaps for even ones.
  • Widening a window from each centre until its ends differ finds the longest palindrome in O(n²) time and O(1) extra memory; checking every substring costs O(n³).
  • The first mismatch ends a centre, because every longer window around it contains the mismatched pair.
  • Keep the best span on a strictly greater length to return the palindrome that starts first.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.