Palindrome Patterns
Palindromes are a favourite interview topic because the same word covers three genuinely different difficulty tiers:
- Verify one string is a palindrome — two pointers, , Easy.
- Find palindromic substrings inside a string — expand around centre, , Medium.
- Build a palindrome by deleting or inserting — interval DP, , 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.
What you’ll learn
- The two-pointer verification template, and the “at most one deletion” variant.
- Expand around centre: why there are
2n - 12n - 1centres, notnn. - 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.
The cue
Tier 1 — verify with two pointers
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")) # Falsedef 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 time, space. s == s[::-1]s == s[::-1] is also but allocates a
reversed copy, so it is 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:
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")) # Truedef 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")) # TrueTier 2 — expand around centre
Every palindrome has a centre. Grow outward from each possible centre while the characters match; each expansion step is 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 - 12n - 1 centres — nn
single-character centres and n - 1n - 1 gaps.
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"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"| Approach | Time | Space |
|---|---|---|
| Check every substring | ||
DP table is_pal[i][j]is_pal[i][j] | ||
| Expand around centre | ||
| Manacher’s algorithm |
Expand-around-centre is the interview answer: same time as the DP with space, and far less code. Manacher’s is the theoretically optimal solution — know that it exists and is the answer to “can you do better than ”, but nobody sane expects you to derive it live.
Tier 3 — when it becomes DP
Once you may skip characters (subsequence) or modify the string
(insert/delete), centres stop helping and you need interval DP over
(i, j)(i, j) ranges.
The one identity worth memorising:
where is the longest palindromic subsequence — and . 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.
Practice — real LeetCode problems
LC 5 — Longest Palindromic Substring · Medium
Problem. Given a string ss, return the longest palindromic
substring in ss.
Constraints. 1 <= len(s) <= 10001 <= len(s) <= 1000, digits and English letters.
Examples. "babad""babad" gives "bab""bab" — "aba""aba" is also accepted ·
"cbbd""cbbd" gives "bb""bb"
Editorial — approach, complexity, follow-ups
Every palindromic substring is determined by its centre and its radius. Try
all 2n - 12n - 1 centres and expand each while the characters match; the
longest one found is the answer.
Time — 2n - 12n - 1 centres, each expanding at most .
Space beyond the output.
Track (best_left, best_len)(best_left, best_len) instead of building strings inside the loop.
Slicing on every improvement turns the inner work into and the whole
thing into in the worst case (a long run of identical characters).
Follow-ups you should expect:
- “Can you do better than ?” Yes — Manacher’s algorithm is . 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""bbbab"gives44, not33. - “Why is the DP table worse?” Same time but space,
and more code. It is only preferable when you need the full
is_pal[i][j]is_pal[i][j]table for something else — which is exactly what LC 131 Palindrome Partitioning wants.
LC 647 — Palindromic Substrings · Medium
Problem. Given a string ss, return the number of palindromic
substrings in it. Substrings at different positions count separately even
if they are identical.
Constraints. 1 <= len(s) <= 10001 <= len(s) <= 1000, lowercase English letters.
Examples. "abc""abc" gives 33 ("a""a", "b""b", "c""c") ·
"aaa""aaa" gives 66 ("a""a"×3, "aa""aa"×2, "aaa""aaa")
Editorial — approach, complexity, follow-ups
This is LC 5 with the accumulator swapped. Each time an expansion succeeds,
the current [left, right][left, right] window is itself a palindromic substring, so
increment.
Time . Space .
Worth checking "abba""abba" by hand to see all six: "a""a", "b""b", "b""b", "a""a",
"bb""bb", "abba""abba". The four single characters always count — every string of
length nn has at least nn palindromic substrings, which is a useful
sanity floor. "abc""abc" giving exactly 33 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 ( 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.
LC 680 — Valid Palindrome II · Easy
Problem. Given a string ss, return TrueTrue if it can be made a
palindrome by deleting at most one character.
Constraints. 1 <= len(s) <= 10^51 <= len(s) <= 10^5, lowercase English letters.
Examples. "aba""aba" gives TrueTrue (already one) · "abca""abca" gives TrueTrue
(delete "c""c" or "b""b") · "abc""abc" gives FalseFalse
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]s[left] or
s[right]s[right] — no other deletion can fix this particular pair. So branch
once: check s[left+1 .. right]s[left+1 .. right] and s[left .. right-1]s[left .. right-1] as plain
palindromes.
Time — the main loop is and the branch happens at most once, costing another . Space .
Follow-ups you should expect: “At most kk deletions?” — the branching
approach becomes exponential, so switch to DP: the answer is
len(s) - LPS(s) <= klen(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.
LeetCode problem set
| # | Problem | Difficulty | The twist |
|---|---|---|---|
| 125 | Valid Palindrome | Easy | Two pointers, skipping non-alphanumerics; guard the inner skip loops |
| 680 | Valid Palindrome II | Easy | One deletion means one branch — try both sides |
| 9 | Palindrome Number | Easy | No string conversion allowed: reverse only half the digits |
| 234 | Palindrome Linked List | Easy | Find the middle, reverse the second half, compare — space |
| 5 | Longest Palindromic Substring | Medium | Expand around all 2n - 12n - 1 centres |
| 647 | Palindromic Substrings | Medium | Same expansion, counting instead of maximising |
| 131 | Palindrome Partitioning | Medium | Backtracking, ideally with a precomputed is_pal[i][j]is_pal[i][j] table |
| 516 | Longest Palindromic Subsequence | Medium | Subsequence, so interval DP — or LCS(s, reversed(s))LCS(s, reversed(s)) |
| 1312 | Minimum Insertion Steps to Make a String Palindrome | Hard | n - LPS(s)n - LPS(s) — the identity does all the work |
Interview follow-ups
| They ask | What they’re checking | The answer |
|---|---|---|
| “Substring or subsequence?” | Careful reading | Substring is contiguous (centres); subsequence may skip (DP). "bbbab""bbbab" gives 3 vs 4 |
“Why 2n - 12n - 1 centres?” | Whether you found the even case | nn character centres plus n - 1n - 1 gaps, because even-length palindromes centre between characters |
| “Beat ?” | Breadth | Manacher’s algorithm, — name it and sketch the mirror/radius-reuse idea |
| “Why not the DP table?” | Judgement | Same time but space; only worth it when you need the whole table (LC 131) |
“At most kk deletions?” | Knowing when branching dies | Branching goes exponential; use len(s) - LPS(s) <= klen(s) - LPS(s) <= k |
| ” space on a linked list?” | Composition | Fast/slow to the middle, reverse the second half, compare — then consider restoring it |
| “Count distinct palindromic substrings?” | Depth | Deduplicate with a set ( space), or use a palindromic tree (Eertree) |
Edge-case checklist
- Single character — always a palindrome; the answer to LC 5 has length
11, and LC 647 counts11. - Two characters —
"ac""ac"gives"a""a"(or"c""c");"bb""bb"gives"bb""bb". The fastest check that you handled even centres. - All identical —
"aaaa""aaaa"; the worst case for expansion, and where slicing inside the loop turns into . - No palindrome longer than 1 —
"abc""abc"; make surebest_lenbest_lenstarts at11, not00. - Ties for longest — multiple valid answers; do not assume one exact string.
- All non-alphanumeric (LC 125) —
".,"".,"must returnTrueTrueand must not run off the end of the string. - Mismatch needing the right-side deletion (LC 680) —
"cbbcc""cbbcc"; catches one-sided solutions. - Empty string — vacuously a palindrome, though most of these problems guarantee at least one character.
Recap
- Three tiers, three techniques. Verify with two pointers (), find substrings by expanding around centres (, 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 - 12n - 1centres — always expand both(i, i)(i, i)and(i, i + 1)(i, i + 1). - Track indices, not strings, inside expansion loops; slicing per
improvement can add a factor of
nn. - “At most one deletion” is one branch, not a counter — and you must try both sides.
min insertions = n - LPS(s)min insertions = n - LPS(s), andLPS(s) = LCS(s, reverse(s))LPS(s) = LCS(s, reverse(s))— one identity that collapses several Hard problems into a known DP.- Manacher’s gives ; 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.
If this helped you, consider buying me a coffee ☕
Buy me a coffeeWas this page helpful?
Let us know how we did
