Longest Palindromic Substring

Given a string of up to 1000 letters and digits, return any longest substring that reads the same forwards and backwards — testing mirrors from the middle beats re-checking every pair from the outside.

The memory hook

Every palindrome has a center — expand from each one and keep the widest mirror that survives.

MediumExpand around centerO(n²) timeO(1) space
01

Mirrors grow from the middle out

intuition

A palindrome is fully decided by its middle: fix a center, then walk both ends outward while the pair matches. There are only 2n−1 centers — one on each character (odd lengths) and one in each gap (even lengths) — so trying all of them covers every possible answer, and the outward walk never needs to re-examine an inner pair.

1Plant every centerOdd centers sit on s[i]; even centers sit in the gap i|i+1. Together they generate all palindromes — no other shape exists.
2Grow the mirrorFrom (l, r), compare s[l] with s[r]. While they match, record the window and step both ends one further out; the first mismatch ends this center.
3Keep the widestOnly a strictly longer window replaces the best, so ties keep the earliest occurrence — exactly what the judge accepts for inputs like “babad”.
The one sentence to remember: After each center finishes, the stored window is the longest mirror seen anywhere so far — that running-best pairing is the whole invariant.
02

Watch every center expand

playable trace

Step through “babad”. Each frame compares one outer pair (l, r) around a center; green cells are a surviving mirror, red cells are the mismatch that stops a center, and the lime underline always marks the best window found so far.

Playable trace · expand around center
Frames are generated from the same logic as the code below.
Presets
Current event
Ready — press Play
Best so far
—
Best window
Each center expands until its first mismatch. Press Play.
unread center mirror window mismatch best so far
0 / 0
03

Expand from every middle

the solution
Python · expand around center
class Solution:
    def longestPalindrome(self, s: str) -> str:
        best_l, best_r = 0, 0  # inclusive bounds of the widest mirror yet
        def expand(l, r):
            nonlocal best_l, best_r
            while l >= 0 and r < len(s) and s[l] == s[r]:
                if r - l > best_r - best_l:  # strictly longer wins
                    best_l, best_r = l, r
                l -= 1
                r += 1
        for i in range(len(s)):
            expand(i, i)      # odd: middle is s[i]
            expand(i, i + 1)  # even: middle is the gap i|i+1
        return s[best_l:best_r + 1]

The displayed Python and the trace above share one rule: compare the outer pair, keep strictly-longer windows, stop a center at its first mismatch.

Odd middleexpand(i, i)Starts as the single character s[i] — always a length-1 palindrome — then grows to 3, 5, 7… until the outer pair differs or an end falls off.
Even middleexpand(i, i + 1)Starts as a length-0 gap. If s[i] ≠ s[i+1] it contributes nothing (as in “babad”); if equal it seeds a length-2 mirror like the “bb” in “cbbd”.
Widest winsif r - l > best_r - best_lStrictly-longer replacement keeps the earliest longest answer on ties, matching the judge. Bounds stay inclusive, so the slice adds +1.
Why does no palindrome escape? Every palindrome has exactly one middle of one of these two forms, and the expansion from that middle reaches at least its full width before stopping — so the longest answer is examined, and the running best can only end on it.
TimeO(n²)2n−1 centers, each walking at most n/2 steps outward — the nested loop charges each center independently, not amortized.
SpaceO(1)Four integers (l, r, best_l, best_r) besides the input and the returned slice; no table is stored.
04

Two quick mental checks

make it stick
s = "babad"
→ "bab" (or "aba")

Odd center 1 grows b·a·b to width 3; every even gap mismatches at once. The trace's first lime upgrade happens at that frame — either accepted answer proves ties keep the earliest.

s = "cbbd"
→ "bb"

All odd centers stall at width 1, but the even gap 1|2 seeds “bb” and then dies on c vs d. Single character “a” is the edge: both loops still run, best stays s[0:1].

What to say out loud
I try every center — each character for odd lengths, each gap for even — expand while the outer pair matches, and track the widest window with constant extra space. That is quadratic time; Manacher's reuses mirror symmetry to make it linear, which I can sketch as a follow-up.