Hi👋SpeedAlgo • Deliberate Practice & Cognitive Ergonomics for Software Engineers

An interactive algorithm mastery and technical interview preparation platform published by Hi👋WebEnterprise. Built for senior and staff software engineers preparing for rigorous coding screens at top tech companies (FAANG/MAMAA).

13 Core Algorithmic Patterns & 225 Practice Problems

  • 1. Two Pointers (16 Paradigms, 43 Problems): Opposite ends, sorted pair sums, palindrome verification, trapping rain water, 3Sum, container with most water — includes the Sliding Window and Fast & Slow Pointers paradigms (contiguous subarray invariants, longest substrings, Floyd's cycle detection, linked list middle).
  • 2. Binary Search (9 Paradigms, 14 Problems): Monotonic predicate partitioning (feasible(x)), boundary search, rotated sorted arrays, square roots, minimum ship capacity, median of two sorted arrays, matrix median on value range.
  • 3. Bit Manipulation (5 Paradigms, 11 Problems): Counting set bits, XOR cancellation, bit reversal, bitmask pairing, maximum XOR with a bitwise trie.
  • 4. Math & Geometry (8 Paradigms, 14 Problems): Sieve of Eratosthenes, integer/roman conversions, modular arithmetic, geometric simulation.
  • 5. Tree/Graph Depth-First Search (15 Paradigms, 24 Problems): Path sums, lowest common ancestor, tree diameter, validating BSTs, tree DP (House Robber III, Binary Tree Cameras).
  • 6. Tree/Graph Breadth-First Search (5 Paradigms, 12 Problems): Level-order traversals, shortest path in unweighted graphs, zig-zag traversals, rotting oranges, word ladders.
  • 7. Graphs (12 Paradigms, 22 Problems): Topological sort, cycle detection, Dijkstra's algorithm, bipartite graph validation, network delay.
  • 8. Backtracking (6 Paradigms, 14 Problems): Subsets, permutations, combinations, constraint satisfaction, pruning, N-Queens, Sudoku solver.
  • 9. Dynamic Programming (13 Paradigms, 28 Problems): Memoization vs tabulation, 0/1 knapsack, unbounded knapsack, coin change, edit distance, longest common subsequence.
  • 10. Heap / Priority Queue (9 Paradigms, 11 Problems): Running medians, top-k elements, interval scheduling, IPO, k-way merges.
  • 11. Advanced Data Structures (7 Paradigms, 17 Problems): Trie (prefix tree), Union-Find (Disjoint Set Union), LRU Cache, LFU Cache, range-sum queries with updates.
  • 12. Stack (5 Paradigms, 9 Problems): Next greater element, largest rectangle in histogram, remove k digits, decode string, min stack.
  • 13. Intervals & Greedy (2 Paradigms, 6 Problems): Merge intervals, interval scheduling, meeting rooms by sweep line, jump game.

4-Stage Deliberate Practice Framework

  1. Stage 1 (Compare & Learn): Read the pattern's mental model, loop invariant and Big-O proof, compare the pattern's template with the problem's solution side by side, and use the step-by-step debugger. The solution can also be shown in C#, Java, TypeScript, C++, Go or Rust for almost every problem.
  2. Stage 2 (Active Recall): Rebuild the solution from memory in a code editor, with an invariant checklist and optional progressive hints, and check it against a judged test suite.
  3. Stage 3 (AI Interview Coach, coming soon): A mock-interview stage still in development; for now, students go straight from Stage 2 to Stage 4.
  4. Stage 4 (Solve on Your Own): Solve a new problem on your own against a 15-minute timer; your code is graded against a test suite. Python and TypeScript run in your browser and C# runs on our server (design problems grade in Python and TypeScript).

Equipped with a spaced-repetition review hub on a fixed 7-step schedule (1 day up to 90 days), a two-pane Studio workspace, and study notes.

Pricing, Access & Commercial Terms

  • Core Curriculum: 100% Free. No credit card required.
  • Coins: New accounts get 40 free coins once, the daily claim adds +20 coins once a day, and inviting a friend earns you +25 coins when they complete their first practice step. These are the only ways to earn coins.
  • AI Coach: Each AI coach question costs 1-5 coins, priced by the server according to its size.
  • BYOK (Bring Your Own Key): AI coaching is free with your own Google Gemini, OpenAI or OpenRouter API key (BYOK).
  • Extras: Coins also unlock extras for 24 hours: a Recall hint level (1 coin), a Solve solution reveal (2), a LeetCode problem card's solutions (1) and a pattern's extra canonical problems (1).
  • Payments & Refunds: Nothing on the site is sold for money: coins can only be earned, never bought. There are no subscriptions or charges, so there is nothing to refund.
  • Platform Operator: Hi👋WebEnterprise. Support & policies at hispeedalgo.com.
Skip to main content
Hi👋SpeedAlgo

Invariant-First Algorithmic Mastery

Arena
238Items
Theory Context•Two Pointers & Sliding Window

Builds on Converging Pointers, which you haven’t finished yet.

P1MediumBuilt on Converging Pointers~4 min speedrun

Expand Around Center (Longest Palindromic Substring)

Built on
Converging PointersStart both pointers at a center and move them outward while the two ends still match.
The problem

Longest Palindromic Substring (LC 5)(opens leetcode.com)

You get one string s made of English letters and digits. A palindrome is a string that is spelled the same from left to right as from right to left, like "racecar" or "abba"; an uppercase letter and its lowercase form are different characters. A substring is a run of neighbouring characters of s, taken without skipping any.

Among all substrings of s that are palindromes, find one with the greatest length and return it. If several different substrings share that greatest length, returning any one of them is correct. A single character is always a palindrome, so the answer is never empty.

Example 1
Input:s = "babad"
Output:"bab"
"bab" (indices 0 to 2) and "aba" (indices 1 to 3) are both palindromes of length 3, and no longer substring is one, so either answer is accepted.
s
b0a1b2a3d4
answer (or "aba")
b0a1b2

Longest Palindromic Substring

string ➔ expand from each center while both ends match ➔ longest palindrome
Spot it when: You need the longest substring of one string that reads the same in both directions, or how many of its substrings do (a run of neighbouring letters, not letters picked with gaps).
Longest Palindromic Substring
Python
1def longest_palindrome(s: str) -> str:
2 """Longest Palindromic Substring: grow a palindrome outward from each of the 2N - 1 centers.
3 Time: O(N^2) | Space: O(1)
4 """
5 n = len(s)
6 start, length = 0, 1 # Best so far: s[start:start + length]; one letter is always a palindrome
7 
8 def expand(lo: int, hi: int) -> int:
9 """Length of the longest palindrome around the center (lo, hi)."""
10 while lo >= 0 and hi < n and s[lo] == s[hi]:
11 lo -= 1 # Both ends match: grow one letter on each side
12 hi += 1
13 return hi - lo - 1 # The loop stopped one step past each end
14 
15 for i in range(n):
16 odd = expand(i, i) # Center on the letter s[i]: "aba"
17 even = expand(i, i + 1) # Center on the gap after s[i]: "abba"
18 if max(odd, even) > length:
19 length = max(odd, even)
20 start = i - (length - 1) // 2 # The middle is i (odd) or the gap after i (even)
21 return s[start:start + length]
Invariant: Inside expand(lo, hi), s[lo + 1:hi] is always a palindrome, and s[start:start + length] is the longest palindrome around every center already expanded.

A palindrome is a mirror, so it is fixed by its middle: instead of testing substrings one by one, stand on each middle and grow outward, which is what expand(lo, hi) does, stepping lo left and hi right while s[lo] == s[hi] and returning hi - lo - 1, the length of the palindrome it grew. This is correct because a palindrome with both end letters removed is still a palindrome with the same middle, so growing from the middle meets every palindrome around it, shortest first, and the first mismatch rules out every longer one; a string of n letters has only 2n - 1 middles, so trying them all finds the longest, which length and start = i - (length - 1) // 2 record. The trap is the even palindromes: "abba" has no middle letter, its middle is the gap between the two bs, so every i needs a second call, expand(i, i + 1), beside expand(i, i); with the odd call alone, "cbbd" returns "c" instead of "bb".

See it run

Longest Palindromic Substring, s = "cbbd"

start 0 · length 1

start 0 · length 1 · odd 1

start 0 · length 1 · even 0

start 0 · length 1 · odd 1

start 0 · length 1 · even 2

start 1 · length 2 · odd 1 · even 2

start 1 · length 2 · odd 1

start 1 · length 2 · even 0

start 1 · length 2 · odd 1

start 1 · length 2 · even 0

start 1 · length 2 · answer bb

The top row is the string. The bottom row shows the best palindrome found so far.

  • center the letter or the pair of letters where expand starts
  • grown a letter that expand added because both ends matched
  • stopped a letter that does not match, which ends the growth
  • best the longest palindrome found so far

A palindrome is fixed by its center. start = 0 and length = 1, because one letter is always a palindrome.

expand(0, 0) grows "c" and stops at lo = -1, hi = 1. It returns 1.

expand(0, 1) stops at once, because "c" and "b" differ. It returns 0.

expand(1, 1) grows "b" and stops at lo = 0, hi = 2. It returns 1.

expand(1, 2) grows "bb" and stops at lo = 0, hi = 3. It returns 2.

length = 2 and start = 1, so the best is "bb". A loop with only expand(i, i) never makes this call and would return "c".

expand(2, 2) grows "b" and stops at lo = 1, hi = 3. It returns 1.

expand(2, 3) stops at once, because "b" and "d" differ. It returns 0.

expand(3, 3) grows "d" and stops at lo = 2, hi = 4. It returns 1.

expand(3, 4) stops at once, because hi = 4 is past the end. It returns 0.

Every center is done. s[start:start + length] = "bb" is the answer.

s
0c
1b
2b
3d
best
cbest
1 / 11
Gotchas1 of 5

Gotcha 1 of 5

  1. 1
    The trap

    Gotcha 1: Leaving out the even center expand(i, i + 1)

    Mistake
    odd = expand(i, i)
    if odd > length:
        length = odd
    Fix
    odd = expand(i, i)
    even = expand(i, i + 1)
    if max(odd, even) > length:
        length = max(odd, even)

    What happens: An even palindrome such as "bb" has no middle letter. Its middle is the gap between two letters. expand(i, i) always starts on a letter, so it never finds "bb". expand(i, i + 1) starts on the gap after s[i].

    Example: s = "cbbd"
    Mistake: returns 'c'Fix: returns 'bb'
  2. 2

    Gotcha 2: Leaving out the lo >= 0 bound

    Mistake
    while hi < n and s[lo] == s[hi]:
    Fix
    while lo >= 0 and hi < n and s[lo] == s[hi]:

    What happens: When lo reaches -1, the palindrome already reaches the start of s. In Python, s[-1] is the last letter, not an error. Without lo >= 0, the loop compares that last letter and can keep growing. The length and the start then come out wrong.

    Example: s = "aa"
    Mistake: returns 'a'Fix: returns 'aa'
  3. 3

    Gotcha 3: Returning hi - lo + 1 from expand

    Mistake
    return hi - lo + 1
    Fix
    return hi - lo - 1

    What happens: The while loop stops one step past each end. So the palindrome it found is s[lo + 1:hi]. Its length is hi - lo - 1. hi - lo + 1 is 2 too large. The start computed from that length is wrong as well.

    Example: s = "babad"
    Mistake: returns ''Fix: returns 'bab'
  4. 4

    Gotcha 4: Computing start as i - length // 2

    Mistake
    start = i - length // 2
    Fix
    start = i - (length - 1) // 2

    What happens: For an odd palindrome centered on s[i], both formulas give the same start. For an even one, the middle is the gap after s[i]. i - length // 2 then starts one letter too early.

    Example: s = "cbbd"
    Mistake: returns 'cb'Fix: returns 'bb'
  5. 5

    Gotcha 5: Slicing a new substring at every center

    What happens: s[lo + 1:hi] copies up to n letters into a new string. Doing it at every center costs O(N) extra memory and more copying. Keep two integers, start and length, and slice once in the return. The answer is the same.

Not this one if…
  • The palindrome may skip letters, a subsequence you get by deleting characters, as in Valid Palindrome III (LC 1216), not a run of neighbouring letters. See Interval DP

Deep Dive

Why growing from a center meets every palindrome

A palindrome reads the same in both directions. It is fixed by its center: the letters on the left of the center mirror the letters on the right. If you remove the first and the last letter of a palindrome, what is left is still a palindrome with the same center.

So expand(lo, hi) starts on a center and grows it. While lo >= 0, hi < n and s[lo] == s[hi], it moves lo one letter left and hi one letter right. The palindromes around this center appear from the shortest to the longest. The first mismatch ends the loop. A longer palindrome around the same center would need that mismatched pair to match, so none exists.

The loop stops one step past each end of the palindrome. So the palindrome is s[lo + 1:hi], and its length is hi - lo - 1.

A string of n letters has 2n - 1 centers: n letters and n - 1 gaps between two letters. Every palindromic substring has exactly one center. So trying every center finds the longest palindrome. The board does this for s = "cbbd", one expand call at a time.

Odd centers, even centers and the trap

A palindrome with an odd length, such as "aba", has a middle letter. A palindrome with an even length, such as "abba", has no middle letter. Its center is the gap between the two middle letters.

expand(i, i) starts on the letter s[i], so it only finds odd palindromes. expand(i, i + 1) starts on the gap after s[i], so it only finds even palindromes. Each i needs both calls.

In the board, expand(1, 2) is the call that finds "bb": the letters at 1 and 2 match, then lo = 0 holds "c" and hi = 3 holds "d", which differ. It returns 2. Every expand(i, i) call returns 1 for "cbbd". A loop with only the odd call keeps "c" and returns the wrong answer.

The other example of Longest Palindromic Substring (LC 5) has s = "babad". The call expand(1, 1) grows "b" to "bab", so the function returns "bab". "aba" is also accepted, because the problem allows either.

Why start = i - (length - 1) // 2

Only start and length are stored. The slice s[start:start + length] is made once, in the return.

For an odd palindrome of length 3 centered on i = 1, there is one letter on each side of i, so start = 0. The formula gives 1 - (3 - 1) // 2 = 0. For an even palindrome of length 2 whose center is the gap after i = 1, the left half has one letter and it is s[1]. The formula gives 1 - (2 - 1) // 2 = 1. In both cases the left half has (length - 1) // 2 letters before i.

A shorter formula, i - length // 2, gives the same start for odd lengths. For the even "bb" it gives 1 - 1 = 0, and the answer is "cb".

The bound lo >= 0 is also needed. In Python, s[-1] is the last letter, not an error. For s = "aa" without the bound, the loop compares the first a with the last one, keeps growing, and the function returns "a" instead of "aa".

Cost and related problems

There are 2n - 1 centers, and one expand call can grow up to about n / 2 times. So the time is O(N^2). For s made of ten letters a, the loops grow 55 times in total. The space is O(1), because only start, length and a few indices are stored.

A table of all pairs (lo, hi) takes O(N^2) time too, but it also takes O(N^2) space. Manacher's algorithm finds the answer in O(N) time. It is rarely needed in an interview, and expansion is much shorter to write.

Palindromic Substrings (LC 647) counts all palindromic substrings instead of finding the longest. The same expansion works. Each growth step of a call is one palindrome. expand(i, i) with result L adds (L + 1) // 2 palindromes, and expand(i, i + 1) adds L // 2. For "aaa" the total is 6.

This card needs letters next to each other. If the palindrome may skip letters, as in Longest Palindromic Subsequence (LC 516) or Valid Palindrome III (LC 1216), the center idea does not apply and a table over (lo, hi) is the standard approach.

60-Second Pitch

"A palindrome is fixed by its center, so I grow outward from each center instead of testing every substring. expand(lo, hi) moves lo left and hi right while both ends match, and returns hi - lo - 1. A string of n letters has 2n - 1 centers: n letters and n - 1 gaps. So for each i I call expand(i, i) for odd palindromes and expand(i, i + 1) for even ones. I store start and length of the best one, with start = i - (length - 1) // 2, and slice once at the end. The time is O(N^2) and the space is O(1). Manacher's algorithm is O(N), but this is simpler."

Common Questions

What is the time and space complexity?

O(N^2) time. There are 2n - 1 centers, and a call can grow up to about n / 2 times. O(1) space, because the code keeps start, length and a few indices.

Why are there 2n - 1 centers?

An odd palindrome is centered on a letter, so there are n of them. An even palindrome is centered on a gap between two letters, so there are n - 1. The main loop makes 2n calls, and the last even call, expand(n - 1, n), stops at once.

Can you do better than O(N^2)?

Yes. Manacher's algorithm reuses the palindromes it already found and runs in O(N) time. It is hard to derive in an interview, so state it as an option and write the expansion.

How do you count palindromic substrings instead?

Use the same two calls and add up the results. expand(i, i) with result L adds (L + 1) // 2 palindromes, and expand(i, i + 1) adds L // 2. This is Palindromic Substrings (LC 647). For "aaa" the answer is 6.

Why not compare each substring with its reverse?

There are about n^2 / 2 substrings, and each comparison takes up to O(N) time. That gives O(N^3). Growing from a center reuses the work: a palindrome is checked by adding one pair of letters to a smaller palindrome.

SWE Reasoning

Spot it

  • The statement asks for the longest substring that reads the same in both directions (Longest Palindromic Substring (LC 5)).
  • It asks how many substrings are palindromes (Palindromic Substrings (LC 647)).
  • The letters of the palindrome are next to each other in the string. Letters are not picked with gaps.
  • n is about 1000 or less, so O(N^2) time is acceptable.

The invariant, step by step

  1. expand(lo, hi) starts on a center. A center with no letters yet, or one letter, is a palindrome.
  2. If s[lo] == s[hi], the letters at both ends match, so adding them to a palindrome gives a longer palindrome with the same center. lo moves left and hi moves right.
  3. The loop stops at the first mismatch or at a string end. No longer palindrome has this center.
  4. hi - lo - 1 is the length of the palindrome, because both pointers are one step past it.
  5. The main loop calls expand(i, i) and expand(i, i + 1) for every i, so all 2n - 1 centers are covered.
  6. start and length hold the longest palindrome over all centers expanded so far.

Traps

  • Only expand(i, i). Even palindromes are never found. "cbbd" returns "c" instead of "bb".
  • No lo >= 0 check. s[-1] is the last letter in Python, and the loop keeps growing. "aa" returns "a".
  • return hi - lo + 1. The loop stops one step past each end, so the length is hi - lo - 1.
  • start = i - length // 2. For an even palindrome, start is one too small. "cbbd" returns "cb".
  • A new substring at every center. Copying s[lo + 1:hi] each time costs extra memory. Store start and length and slice once.

Practice

Canonical LeetCode:Longest Palindromic Substring (LC 5)Palindromic Substrings (LC 647)
Theory Context•Two Pointers & Sliding Window

Builds on Converging Pointers, which you haven’t finished yet.

P1MediumBuilt on Converging Pointers~4 min speedrun

Expand Around Center (Longest Palindromic Substring)

Built on
Converging PointersStart both pointers at a center and move them outward while the two ends still match.
The problem

Longest Palindromic Substring (LC 5)(opens leetcode.com)

You get one string s made of English letters and digits. A palindrome is a string that is spelled the same from left to right as from right to left, like "racecar" or "abba"; an uppercase letter and its lowercase form are different characters. A substring is a run of neighbouring characters of s, taken without skipping any.

Among all substrings of s that are palindromes, find one with the greatest length and return it. If several different substrings share that greatest length, returning any one of them is correct. A single character is always a palindrome, so the answer is never empty.

Example 1
Input:s = "babad"
Output:"bab"
"bab" (indices 0 to 2) and "aba" (indices 1 to 3) are both palindromes of length 3, and no longer substring is one, so either answer is accepted.
s
b0a1b2a3d4
answer (or "aba")
b0a1b2

Longest Palindromic Substring

string ➔ expand from each center while both ends match ➔ longest palindrome
Spot it when: You need the longest substring of one string that reads the same in both directions, or how many of its substrings do (a run of neighbouring letters, not letters picked with gaps).
Longest Palindromic Substring
Python
1def longest_palindrome(s: str) -> str:
2 """Longest Palindromic Substring: grow a palindrome outward from each of the 2N - 1 centers.
3 Time: O(N^2) | Space: O(1)
4 """
5 n = len(s)
6 start, length = 0, 1 # Best so far: s[start:start + length]; one letter is always a palindrome
7 
8 def expand(lo: int, hi: int) -> int:
9 """Length of the longest palindrome around the center (lo, hi)."""
10 while lo >= 0 and hi < n and s[lo] == s[hi]:
11 lo -= 1 # Both ends match: grow one letter on each side
12 hi += 1
13 return hi - lo - 1 # The loop stopped one step past each end
14 
15 for i in range(n):
16 odd = expand(i, i) # Center on the letter s[i]: "aba"
17 even = expand(i, i + 1) # Center on the gap after s[i]: "abba"
18 if max(odd, even) > length:
19 length = max(odd, even)
20 start = i - (length - 1) // 2 # The middle is i (odd) or the gap after i (even)
21 return s[start:start + length]
Invariant: Inside expand(lo, hi), s[lo + 1:hi] is always a palindrome, and s[start:start + length] is the longest palindrome around every center already expanded.

A palindrome is a mirror, so it is fixed by its middle: instead of testing substrings one by one, stand on each middle and grow outward, which is what expand(lo, hi) does, stepping lo left and hi right while s[lo] == s[hi] and returning hi - lo - 1, the length of the palindrome it grew. This is correct because a palindrome with both end letters removed is still a palindrome with the same middle, so growing from the middle meets every palindrome around it, shortest first, and the first mismatch rules out every longer one; a string of n letters has only 2n - 1 middles, so trying them all finds the longest, which length and start = i - (length - 1) // 2 record. The trap is the even palindromes: "abba" has no middle letter, its middle is the gap between the two bs, so every i needs a second call, expand(i, i + 1), beside expand(i, i); with the odd call alone, "cbbd" returns "c" instead of "bb".

See it run

Longest Palindromic Substring, s = "cbbd"

start 0 · length 1

start 0 · length 1 · odd 1

start 0 · length 1 · even 0

start 0 · length 1 · odd 1

start 0 · length 1 · even 2

start 1 · length 2 · odd 1 · even 2

start 1 · length 2 · odd 1

start 1 · length 2 · even 0

start 1 · length 2 · odd 1

start 1 · length 2 · even 0

start 1 · length 2 · answer bb

The top row is the string. The bottom row shows the best palindrome found so far.

  • center the letter or the pair of letters where expand starts
  • grown a letter that expand added because both ends matched
  • stopped a letter that does not match, which ends the growth
  • best the longest palindrome found so far

A palindrome is fixed by its center. start = 0 and length = 1, because one letter is always a palindrome.

expand(0, 0) grows "c" and stops at lo = -1, hi = 1. It returns 1.

expand(0, 1) stops at once, because "c" and "b" differ. It returns 0.

expand(1, 1) grows "b" and stops at lo = 0, hi = 2. It returns 1.

expand(1, 2) grows "bb" and stops at lo = 0, hi = 3. It returns 2.

length = 2 and start = 1, so the best is "bb". A loop with only expand(i, i) never makes this call and would return "c".

expand(2, 2) grows "b" and stops at lo = 1, hi = 3. It returns 1.

expand(2, 3) stops at once, because "b" and "d" differ. It returns 0.

expand(3, 3) grows "d" and stops at lo = 2, hi = 4. It returns 1.

expand(3, 4) stops at once, because hi = 4 is past the end. It returns 0.

Every center is done. s[start:start + length] = "bb" is the answer.

s
0c
1b
2b
3d
best
cbest
1 / 11
Gotchas1 of 5

Gotcha 1 of 5

  1. 1
    The trap

    Gotcha 1: Leaving out the even center expand(i, i + 1)

    Mistake
    odd = expand(i, i)
    if odd > length:
        length = odd
    Fix
    odd = expand(i, i)
    even = expand(i, i + 1)
    if max(odd, even) > length:
        length = max(odd, even)

    What happens: An even palindrome such as "bb" has no middle letter. Its middle is the gap between two letters. expand(i, i) always starts on a letter, so it never finds "bb". expand(i, i + 1) starts on the gap after s[i].

    Example: s = "cbbd"
    Mistake: returns 'c'Fix: returns 'bb'
  2. 2

    Gotcha 2: Leaving out the lo >= 0 bound

    Mistake
    while hi < n and s[lo] == s[hi]:
    Fix
    while lo >= 0 and hi < n and s[lo] == s[hi]:

    What happens: When lo reaches -1, the palindrome already reaches the start of s. In Python, s[-1] is the last letter, not an error. Without lo >= 0, the loop compares that last letter and can keep growing. The length and the start then come out wrong.

    Example: s = "aa"
    Mistake: returns 'a'Fix: returns 'aa'
  3. 3

    Gotcha 3: Returning hi - lo + 1 from expand

    Mistake
    return hi - lo + 1
    Fix
    return hi - lo - 1

    What happens: The while loop stops one step past each end. So the palindrome it found is s[lo + 1:hi]. Its length is hi - lo - 1. hi - lo + 1 is 2 too large. The start computed from that length is wrong as well.

    Example: s = "babad"
    Mistake: returns ''Fix: returns 'bab'
  4. 4

    Gotcha 4: Computing start as i - length // 2

    Mistake
    start = i - length // 2
    Fix
    start = i - (length - 1) // 2

    What happens: For an odd palindrome centered on s[i], both formulas give the same start. For an even one, the middle is the gap after s[i]. i - length // 2 then starts one letter too early.

    Example: s = "cbbd"
    Mistake: returns 'cb'Fix: returns 'bb'
  5. 5

    Gotcha 5: Slicing a new substring at every center

    What happens: s[lo + 1:hi] copies up to n letters into a new string. Doing it at every center costs O(N) extra memory and more copying. Keep two integers, start and length, and slice once in the return. The answer is the same.

Not this one if…
  • The palindrome may skip letters, a subsequence you get by deleting characters, as in Valid Palindrome III (LC 1216), not a run of neighbouring letters. See Interval DP

Deep Dive

Why growing from a center meets every palindrome

A palindrome reads the same in both directions. It is fixed by its center: the letters on the left of the center mirror the letters on the right. If you remove the first and the last letter of a palindrome, what is left is still a palindrome with the same center.

So expand(lo, hi) starts on a center and grows it. While lo >= 0, hi < n and s[lo] == s[hi], it moves lo one letter left and hi one letter right. The palindromes around this center appear from the shortest to the longest. The first mismatch ends the loop. A longer palindrome around the same center would need that mismatched pair to match, so none exists.

The loop stops one step past each end of the palindrome. So the palindrome is s[lo + 1:hi], and its length is hi - lo - 1.

A string of n letters has 2n - 1 centers: n letters and n - 1 gaps between two letters. Every palindromic substring has exactly one center. So trying every center finds the longest palindrome. The board does this for s = "cbbd", one expand call at a time.

Odd centers, even centers and the trap

A palindrome with an odd length, such as "aba", has a middle letter. A palindrome with an even length, such as "abba", has no middle letter. Its center is the gap between the two middle letters.

expand(i, i) starts on the letter s[i], so it only finds odd palindromes. expand(i, i + 1) starts on the gap after s[i], so it only finds even palindromes. Each i needs both calls.

In the board, expand(1, 2) is the call that finds "bb": the letters at 1 and 2 match, then lo = 0 holds "c" and hi = 3 holds "d", which differ. It returns 2. Every expand(i, i) call returns 1 for "cbbd". A loop with only the odd call keeps "c" and returns the wrong answer.

The other example of Longest Palindromic Substring (LC 5) has s = "babad". The call expand(1, 1) grows "b" to "bab", so the function returns "bab". "aba" is also accepted, because the problem allows either.

Why start = i - (length - 1) // 2

Only start and length are stored. The slice s[start:start + length] is made once, in the return.

For an odd palindrome of length 3 centered on i = 1, there is one letter on each side of i, so start = 0. The formula gives 1 - (3 - 1) // 2 = 0. For an even palindrome of length 2 whose center is the gap after i = 1, the left half has one letter and it is s[1]. The formula gives 1 - (2 - 1) // 2 = 1. In both cases the left half has (length - 1) // 2 letters before i.

A shorter formula, i - length // 2, gives the same start for odd lengths. For the even "bb" it gives 1 - 1 = 0, and the answer is "cb".

The bound lo >= 0 is also needed. In Python, s[-1] is the last letter, not an error. For s = "aa" without the bound, the loop compares the first a with the last one, keeps growing, and the function returns "a" instead of "aa".

Cost and related problems

There are 2n - 1 centers, and one expand call can grow up to about n / 2 times. So the time is O(N^2). For s made of ten letters a, the loops grow 55 times in total. The space is O(1), because only start, length and a few indices are stored.

A table of all pairs (lo, hi) takes O(N^2) time too, but it also takes O(N^2) space. Manacher's algorithm finds the answer in O(N) time. It is rarely needed in an interview, and expansion is much shorter to write.

Palindromic Substrings (LC 647) counts all palindromic substrings instead of finding the longest. The same expansion works. Each growth step of a call is one palindrome. expand(i, i) with result L adds (L + 1) // 2 palindromes, and expand(i, i + 1) adds L // 2. For "aaa" the total is 6.

This card needs letters next to each other. If the palindrome may skip letters, as in Longest Palindromic Subsequence (LC 516) or Valid Palindrome III (LC 1216), the center idea does not apply and a table over (lo, hi) is the standard approach.

60-Second Pitch

"A palindrome is fixed by its center, so I grow outward from each center instead of testing every substring. expand(lo, hi) moves lo left and hi right while both ends match, and returns hi - lo - 1. A string of n letters has 2n - 1 centers: n letters and n - 1 gaps. So for each i I call expand(i, i) for odd palindromes and expand(i, i + 1) for even ones. I store start and length of the best one, with start = i - (length - 1) // 2, and slice once at the end. The time is O(N^2) and the space is O(1). Manacher's algorithm is O(N), but this is simpler."

Common Questions

What is the time and space complexity?

O(N^2) time. There are 2n - 1 centers, and a call can grow up to about n / 2 times. O(1) space, because the code keeps start, length and a few indices.

Why are there 2n - 1 centers?

An odd palindrome is centered on a letter, so there are n of them. An even palindrome is centered on a gap between two letters, so there are n - 1. The main loop makes 2n calls, and the last even call, expand(n - 1, n), stops at once.

Can you do better than O(N^2)?

Yes. Manacher's algorithm reuses the palindromes it already found and runs in O(N) time. It is hard to derive in an interview, so state it as an option and write the expansion.

How do you count palindromic substrings instead?

Use the same two calls and add up the results. expand(i, i) with result L adds (L + 1) // 2 palindromes, and expand(i, i + 1) adds L // 2. This is Palindromic Substrings (LC 647). For "aaa" the answer is 6.

Why not compare each substring with its reverse?

There are about n^2 / 2 substrings, and each comparison takes up to O(N) time. That gives O(N^3). Growing from a center reuses the work: a palindrome is checked by adding one pair of letters to a smaller palindrome.

SWE Reasoning

Spot it

  • The statement asks for the longest substring that reads the same in both directions (Longest Palindromic Substring (LC 5)).
  • It asks how many substrings are palindromes (Palindromic Substrings (LC 647)).
  • The letters of the palindrome are next to each other in the string. Letters are not picked with gaps.
  • n is about 1000 or less, so O(N^2) time is acceptable.

The invariant, step by step

  1. expand(lo, hi) starts on a center. A center with no letters yet, or one letter, is a palindrome.
  2. If s[lo] == s[hi], the letters at both ends match, so adding them to a palindrome gives a longer palindrome with the same center. lo moves left and hi moves right.
  3. The loop stops at the first mismatch or at a string end. No longer palindrome has this center.
  4. hi - lo - 1 is the length of the palindrome, because both pointers are one step past it.
  5. The main loop calls expand(i, i) and expand(i, i + 1) for every i, so all 2n - 1 centers are covered.
  6. start and length hold the longest palindrome over all centers expanded so far.

Traps

  • Only expand(i, i). Even palindromes are never found. "cbbd" returns "c" instead of "bb".
  • No lo >= 0 check. s[-1] is the last letter in Python, and the loop keeps growing. "aa" returns "a".
  • return hi - lo + 1. The loop stops one step past each end, so the length is hi - lo - 1.
  • start = i - length // 2. For an even palindrome, start is one too small. "cbbd" returns "cb".
  • A new substring at every center. Copying s[lo + 1:hi] each time costs extra memory. Store start and length and slice once.

Practice

Canonical LeetCode:Longest Palindromic Substring (LC 5)Palindromic Substrings (LC 647)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•