Builds on Converging Pointers, which you haven’t finished yet.
Expand Around Center (Longest Palindromic Substring)
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.
"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.Longest Palindromic Substring
string ➔ expand from each center while both ends match ➔ longest palindrome| 1 | def 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] |
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 0length 1
The top row is the string. The bottom row shows the best palindrome found so far.
centerthe letter or the pair of letters where expand startsgrowna letter that expand added because both ends matchedstoppeda letter that does not match, which ends the growthbestthe longest palindrome found so far
A palindrome is fixed by its center. start = 0 and length = 1, because one letter is always a palindrome.
Gotcha 1 of 5
- The trap
Gotcha 1: Leaving out the even center
expand(i, i + 1)Mistakeodd = expand(i, i) if odd > length: length = oddFixodd = 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 afters[i].Example: s = "cbbd"Mistake: returns 'c'Fix: returns 'bb'
- 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 - 1centers, and a call can grow up to aboutn / 2times.O(1)space, because the code keepsstart,lengthand a few indices.- Why are there 2n - 1 centers?
An odd palindrome is centered on a letter, so there are
nof them. An even palindrome is centered on a gap between two letters, so there aren - 1. The main loop makes2ncalls, 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 resultLadds(L + 1) // 2palindromes, andexpand(i, i + 1)addsL // 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 / 2substrings, and each comparison takes up toO(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.
nis about 1000 or less, so O(N^2) time is acceptable.
The invariant, step by step
expand(lo, hi)starts on a center. A center with no letters yet, or one letter, is a palindrome.- 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.lomoves left andhimoves right. - The loop stops at the first mismatch or at a string end. No longer palindrome has this center.
hi - lo - 1is the length of the palindrome, because both pointers are one step past it.- The main loop calls
expand(i, i)andexpand(i, i + 1)for everyi, so all2n - 1centers are covered. startandlengthhold 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 >= 0check.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 ishi - lo - 1.start = i - length // 2. For an even palindrome,startis one too small."cbbd"returns"cb".- A new substring at every center. Copying
s[lo + 1:hi]each time costs extra memory. Storestartandlengthand slice once.
Practice
Builds on Converging Pointers, which you haven’t finished yet.
Expand Around Center (Longest Palindromic Substring)
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.
"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.Longest Palindromic Substring
string ➔ expand from each center while both ends match ➔ longest palindrome| 1 | def 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] |
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 0length 1
The top row is the string. The bottom row shows the best palindrome found so far.
centerthe letter or the pair of letters where expand startsgrowna letter that expand added because both ends matchedstoppeda letter that does not match, which ends the growthbestthe longest palindrome found so far
A palindrome is fixed by its center. start = 0 and length = 1, because one letter is always a palindrome.
Gotcha 1 of 5
- The trap
Gotcha 1: Leaving out the even center
expand(i, i + 1)Mistakeodd = expand(i, i) if odd > length: length = oddFixodd = 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 afters[i].Example: s = "cbbd"Mistake: returns 'c'Fix: returns 'bb'
- 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 - 1centers, and a call can grow up to aboutn / 2times.O(1)space, because the code keepsstart,lengthand a few indices.- Why are there 2n - 1 centers?
An odd palindrome is centered on a letter, so there are
nof them. An even palindrome is centered on a gap between two letters, so there aren - 1. The main loop makes2ncalls, 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 resultLadds(L + 1) // 2palindromes, andexpand(i, i + 1)addsL // 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 / 2substrings, and each comparison takes up toO(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.
nis about 1000 or less, so O(N^2) time is acceptable.
The invariant, step by step
expand(lo, hi)starts on a center. A center with no letters yet, or one letter, is a palindrome.- 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.lomoves left andhimoves right. - The loop stops at the first mismatch or at a string end. No longer palindrome has this center.
hi - lo - 1is the length of the palindrome, because both pointers are one step past it.- The main loop calls
expand(i, i)andexpand(i, i + 1)for everyi, so all2n - 1centers are covered. startandlengthhold 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 >= 0check.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 ishi - lo - 1.start = i - length // 2. For an even palindrome,startis one too small."cbbd"returns"cb".- A new substring at every center. Copying
s[lo + 1:hi]each time costs extra memory. Storestartandlengthand slice once.