Skip to content

Palindrome Patterns

Palindromes are a favourite interview topic because the same word covers three genuinely different difficulty tiers:

  1. Verify one string is a palindrome — two pointers, O(n)O(n), Easy.
  2. Find palindromic substrings inside a string — expand around centre, O(n2)O(n^2), Medium.
  3. Build a palindrome by deleting or inserting — interval DP, O(n2)O(n^2), Medium to Hard.

The mistake that costs the most time is reaching for tier 3 machinery on a tier 2 problem. This page separates them, and gives you the cue for each.

  • The two-pointer verification template, and the “at most one deletion” variant.
  • Expand around centre: why there are 2n - 1 centres, not n.
  • Why “substring” means expand-around-centre and “subsequence” means DP — the single most useful distinction on this page.
  • Three real LeetCode problems solved in the browser: 5, 647, 680.
is_palindrome.py
def is_palindrome(s):
    left, right = 0, len(s) - 1
    while left < right:
        if s[left] != s[right]:
            return False
        left += 1
        right -= 1
    return True
 
 
print(is_palindrome("racecar"))   # True
print(is_palindrome("abca"))      # False

O(n)O(n) time, O(1)O(1) space. s == s[::-1] is also O(n)O(n) but allocates a reversed copy, so it is O(n)O(n) space — fine to mention, and fine to use unless asked for constant space.

For LC 125 the only additions are skipping non-alphanumeric characters and lower-casing:

valid_palindrome_125.py
def is_palindrome_alnum(s):
    left, right = 0, len(s) - 1
    while left < right:
        while left < right and not s[left].isalnum():    # skip junk
            left += 1
        while left < right and not s[right].isalnum():
            right -= 1
        if s[left].lower() != s[right].lower():
            return False
        left += 1
        right -= 1
    return True
 
 
print(is_palindrome_alnum("A man, a plan, a canal: Panama"))   # True

Every palindrome has a centre. Grow outward from each possible centre while the characters match; each expansion step is O(1)O(1) and finds a new palindrome.

The detail that catches people: a palindrome of even length has its centre between two characters. So there are 2n - 1 centres — n single-character centres and n - 1 gaps.

expand_around_centre.py
def longest_palindrome(s):
    if not s:
        return ""
    best_left, best_len = 0, 1
 
    def expand(left, right):
        nonlocal best_left, best_len
        while left >= 0 and right < len(s) and s[left] == s[right]:
            if right - left + 1 > best_len:
                best_left, best_len = left, right - left + 1
            left -= 1
            right += 1
 
    for i in range(len(s)):
        expand(i, i)          # odd-length centre: a single character
        expand(i, i + 1)      # even-length centre: the gap after i
 
    return s[best_left:best_left + best_len]
 
 
print(longest_palindrome("babad"))   # "bab"  ("aba" is equally correct)
print(longest_palindrome("cbbd"))    # "bb"
ApproachTimeSpace
Check every substringO(n3)O(n^3)O(1)O(1)
DP table is_pal[i][j]O(n2)O(n^2)O(n2)O(n^2)
Expand around centreO(n2)O(n^2)O(1)O(1)
Manacher’s algorithmO(n)O(n)O(n)O(n)

Expand-around-centre is the interview answer: same time as the DP with O(1)O(1) space, and far less code. Manacher’s is the theoretically optimal O(n)O(n) solution — know that it exists and is the answer to “can you do better than O(n2)O(n^2)”, but nobody sane expects you to derive it live.

Once you may skip characters (subsequence) or modify the string (insert/delete), centres stop helping and you need interval DP over (i, j) ranges.

The one identity worth memorising:

min insertions to make s a palindrome=nLPS(s)\text{min insertions to make } s \text{ a palindrome} = n - \text{LPS}(s)

where LPS\text{LPS} is the longest palindromic subsequence — and LPS(s)=LCS(s,reverse(s))\text{LPS}(s) = \text{LCS}(s, \text{reverse}(s)). So LC 1312 reduces to a longest-common-subsequence you already know how to write. These live in Classic DP and DP on Grids and Intervals.

A palindrome is defined by its centre, and there are only 2n12n - 1 centres. That reframing is what turns an O(n3)O(n^3) brute force into O(n2)O(n^2):

arrayExpand from every centre — odd and evenLC 5 · O(n^2) time, O(1) space
b0a1b2a3d4
centres9
setupA palindrome is defined by its **centre**, and there are only $2n - 1$ centres: $n$ single characters (odd lengths) and $n - 1$ gaps between characters (even lengths). Checking every substring is $O(n^3)$; expanding from every centre is $O(n^2)$ with no extra memory.
1/19

Both centre kinds must be tried at every index: n single characters for odd lengths, n-1 gaps for even ones. Checking only odd centres misses 'abba' entirely, and that omission passes a surprising number of test cases.

ApproachTimeSpace
Check every substringO(n3)O(n^3)O(1)O(1)
DP over [i][j] is-palindromeO(n2)O(n^2)O(n2)O(n^2)
Expand around centreO(n2)O(n^2)O(1)O(1)
Manacher’s algorithmO(n)O(n)O(n)O(n)

Expand-around-centre is the answer to give: same time as the DP with none of the space. Manacher’s is worth naming — it shows you know O(n)O(n) exists — but almost never worth writing, and saying “I know it exists and would look it up” reads better than a half-remembered attempt.

LC 5 — Longest Palindromic Substring · Medium

Section titled “LC 5 — Longest Palindromic Substring · Medium”

Problem. Given a string s, return the longest palindromic substring in s.

Constraints. 1 <= len(s) <= 1000, digits and English letters.

Examples. "babad" gives "bab""aba" is also accepted · "cbbd" gives "bb"

Editorial — approach, complexity, follow-ups

Every palindromic substring is determined by its centre and its radius. Try all 2n - 1 centres and expand each while the characters match; the longest one found is the answer.

Time O(n2)O(n^2)2n - 1 centres, each expanding at most O(n)O(n). Space O(1)O(1) beyond the output.

Track (best_left, best_len) instead of building strings inside the loop. Slicing on every improvement turns the inner work into O(n)O(n) and the whole thing into O(n3)O(n^3) in the worst case (a long run of identical characters).

Follow-ups you should expect:

  • “Can you do better than O(n2)O(n^2)?” Yes — Manacher’s algorithm is O(n)O(n). It transforms the string (interleaving separators so every palindrome is odd-length) and reuses previously computed radii via a mirror argument. Naming it and sketching why it works is the expected depth; deriving it live is not.
  • “Count them instead of finding the longest?” LC 647 — same expansion, increment a counter on every successful step.
  • “Longest palindromic subsequence?” Different problem entirely — interval DP, LC 516. "bbbab" gives 4, not 3.
  • “Why is the DP table worse?” Same O(n2)O(n^2) time but O(n2)O(n^2) space, and more code. It is only preferable when you need the full is_pal[i][j] table for something else — which is exactly what LC 131 Palindrome Partitioning wants.

LC 647 — Palindromic Substrings · Medium

Section titled “LC 647 — Palindromic Substrings · Medium”

Problem. Given a string s, return the number of palindromic substrings in it. Substrings at different positions count separately even if they are identical.

Constraints. 1 <= len(s) <= 1000, lowercase English letters.

Examples. "abc" gives 3 ("a", "b", "c") · "aaa" gives 6 ("a"×3, "aa"×2, "aaa")

Editorial — approach, complexity, follow-ups

This is LC 5 with the accumulator swapped. Each time an expansion succeeds, the current [left, right] window is itself a palindromic substring, so increment.

Time O(n2)O(n^2). Space O(1)O(1).

Worth checking "abba" by hand to see all six: "a", "b", "b", "a", "bb", "abba". The four single characters always count — every string of length n has at least n palindromic substrings, which is a useful sanity floor. "abc" giving exactly 3 confirms you are counting singles.

Follow-ups you should expect: “Count distinct palindromic substrings?” — genuinely harder; you need to deduplicate, which means a set of strings (O(n2)O(n^2) space) or the Eertree / palindromic-tree structure for the efficient answer. “Longest instead of count?” — LC 5. “Count palindromic subsequences?” — interval DP, and the counting version (LC 730) is Hard.

Problem. Given a string s, return True if it can be made a palindrome by deleting at most one character.

Constraints. 1 <= len(s) <= 10^5, lowercase English letters.

Examples. "aba" gives True (already one) · "abca" gives True (delete "c" or "b") · "abc" gives False

Editorial — approach, complexity, follow-ups

Walk inward. While characters match there is no decision to make. At the first mismatch, the deleted character must be one of s[left] or s[right] — no other deletion can fix this particular pair. So branch once: check s[left+1 .. right] and s[left .. right-1] as plain palindromes.

Time O(n)O(n) — the main loop is O(n)O(n) and the branch happens at most once, costing another O(n)O(n). Space O(1)O(1).

Follow-ups you should expect: “At most k deletions?” — the branching approach becomes exponential, so switch to DP: the answer is len(s) - LPS(s) <= k, i.e. LC 1312’s identity. “Return which character to delete?” — return the index from whichever branch succeeded. “Deletions from both strings to make them equal?” — LC 583, an edit-distance variant.

Generated from the problem database, so each entry carries its sheet membership and reported companies. Tick them off as you go — progress is saved in this browser, and the Export button writes it to a file you can keep.

10 problems
4 easy4 medium2 hard

Work down the ladder. Tick each problem off as you go — progress is saved in this browser, and the Export button in the filter bar writes it to a file you can keep.

s = "babad". Every centre, both kinds.

centrekindexpansionresultbest so far
0oddb then lo < 0"b" (1)"b"
0–1evenbanone"b"
1odda, then b==bbab, then out of range"bab" (3)"bab"
1–2evenabnone"bab"
2oddb, then a==aaba"aba" (3)"bab" (tie, first kept)
2–3evenbanone
3odda, then bd"a" (1)
3–4evenadnone
4oddd"d" (1)

Answer "bab" — and "aba" is equally valid, which the problem statement explicitly allows. Worth confirming with the interviewer rather than assuming.

VariantChange to the templateCanonical problem
Longest palindromic substringtrack the widest expansion5 Longest Palindromic Substring
Count all palindromic substringsadd 1 per successful expansion step instead of taking a max647 Palindromic Substrings
Is it a palindrome, ignoring non-alphanumericstwo pointers with skip conditions, no centres needed125 Valid Palindrome
Palindrome after deleting one charactertwo pointers; on the first mismatch try skipping either side680 Valid Palindrome II
Longest palindromic subsequencenot contiguous — this is LCS of s and reversed(s)516 Longest Palindromic Subsequence
Partition into palindromesbacktracking with an is-palindrome check, memoised131 · 132 Palindrome Partitioning
They askWhat they’re checkingThe answer
“Substring or subsequence?”Careful readingSubstring is contiguous (centres); subsequence may skip (DP). "bbbab" gives 3 vs 4
“Why 2n - 1 centres?”Whether you found the even casen character centres plus n - 1 gaps, because even-length palindromes centre between characters
“Beat O(n2)O(n^2)?”BreadthManacher’s algorithm, O(n)O(n) — name it and sketch the mirror/radius-reuse idea
“Why not the DP table?”JudgementSame O(n2)O(n^2) time but O(n2)O(n^2) space; only worth it when you need the whole table (LC 131)
“At most k deletions?”Knowing when branching diesBranching goes exponential; use len(s) - LPS(s) <= k
O(1)O(1) space on a linked list?”CompositionFast/slow to the middle, reverse the second half, compare — then consider restoring it
“Count distinct palindromic substrings?”DepthDeduplicate with a set (O(n2)O(n^2) space), or use a palindromic tree (Eertree)
  • Single character — always a palindrome; the answer to LC 5 has length 1, and LC 647 counts 1.
  • Two characters"ac" gives "a" (or "c"); "bb" gives "bb". The fastest check that you handled even centres.
  • All identical"aaaa"; the worst case for expansion, and where slicing inside the loop turns O(n2)O(n^2) into O(n3)O(n^3).
  • No palindrome longer than 1"abc"; make sure best_len starts at 1, not 0.
  • Ties for longest — multiple valid answers; do not assume one exact string.
  • All non-alphanumeric (LC 125) — ".," must return True and must not run off the end of the string.
  • Mismatch needing the right-side deletion (LC 680) — "cbbcc"; catches one-sided solutions.
  • Empty string — vacuously a palindrome, though most of these problems guarantee at least one character.
pch.quizTag Palindrome patterns — self-check
  1. How many centres does a string of length n have, and why does it matter?

    pch.quizShowAnswer

    B — 2n - 1 — n single characters for odd lengths plus n-1 gaps for even ones — Missing the even centres is the classic bug: it silently fails on any even-length palindrome such as abba, while still passing plenty of tests.

  2. After the expansion while-loop exits, why adjust lo and hi by one?

    pch.quizShowAnswer

    B — Because the loop exits only after both pointers have moved past the failing comparison, so the real palindrome is s[lo+1:hi] — The loop condition is checked after the increment, so on exit both pointers sit one step outside. Forgetting the step back reports a length two too large, and single-character answers hide it.

  3. A problem asks for the longest palindromic SUBSEQUENCE. Does expand-around-centre apply?

    pch.quizShowAnswer

    B — No — centres require contiguity. This is LCS of the string against its reverse — One word changes the entire problem and the entire toolset. A palindromic subsequence need not be contiguous, so there is no centre to expand from — it becomes O(n squared) DP.

  4. Counting all palindromic substrings (LC 647) — what changes?

    pch.quizShowAnswer

    B — Add 1 for each successful expansion step rather than tracking a maximum, since every step is itself a distinct palindrome — Each successful expansion from a centre reveals one more palindrome. Same loop, a counter instead of a max — which is why recognising the shared template is worth more than memorising either problem.

  5. Should you write Manacher’s algorithm when asked to beat O(n squared)?

    pch.quizShowAnswer

    B — Name it, explain that it achieves O(n) by reusing mirror information, and say you would look up the details — a half-remembered attempt is worse than a clear acknowledgement — Interviewers are testing awareness of the landscape, not recall of a rarely-used algorithm. Naming it and being honest about the details scores better than a broken implementation.

  • Cue — “palindromic substring” (contiguous). If it says subsequence, stop — that is LCS against the reverse.
  • Key idea — a palindrome is defined by its centre, and there are 2n12n - 1 centres: n characters and n - 1 gaps.
  • Template — for each centre, expand while s[lo] == s[hi], then step back inside by one and measure.
  • ComplexityO(n2)O(n^2) time, O(1)O(1) space. Manacher’s is O(n)O(n) — name it, do not write it.
  • Remember — try both centre kinds at every index; adjust by ±1 after the loop; for counting, add 1 per expansion step instead of taking a max.
  • Three tiers, three techniques. Verify with two pointers (O(n)O(n)), find substrings by expanding around centres (O(n2)O(n^2), O(1)O(1) space), build/modify with interval DP.
  • Substring vs. subsequence is the distinction that decides which tier you are in. Read the problem statement twice.
  • There are 2n - 1 centres — always expand both (i, i) and (i, i + 1).
  • Track indices, not strings, inside expansion loops; slicing per improvement can add a factor of n.
  • “At most one deletion” is one branch, not a counter — and you must try both sides.
  • min insertions = n - LPS(s), and LPS(s) = LCS(s, reverse(s)) — one identity that collapses several Hard problems into a known DP.
  • Manacher’s gives O(n)O(n); know it exists, and that expand-around-centre is what you are expected to write.

Next: Trie Patterns — the prefix tree, and the problems that are unreasonably easy once you have one.

pch.coffeeTagline

pch.coffeeCta

pch.feedbackHeading

pch.feedbackSubheading