DSA sheet · Strings · Two Pointers pattern
Longest Palindromic Substring
In this video the teacher takes a string and finds its longest piece that reads the same from both ends. She first builds the obvious brute force (make every substring, check each one with two pointers), works out why it is about n³, and then flips the palindrome check around: instead of starting at the two ends and shrinking inwards, she starts in the middle and expands outwards. That one change removes a whole loop and gives O(n²). She also explains why two pointers is the right pattern here (and not sliding window), how to handle odd and even palindromes, and how to get the substring back when you only have one index and a length. The same trick is reused in the very next problem (Palindromic Substrings), so it's worth learning well.
Every part below follows the same order:
① the question in simple words → ② what the constraints tell us → ③ intuition → ④ building the logic from examples → ⑤ approach steps → ⑥ code → ⑦ code line by line → ⑧ dry run → ⑨ complexity & remember
- Part 0 · Two pointers from scratch (what this page needs)
- Part A · Brute force: every substring + a two-pointer check
- Part B · Optimal: expand around every center
- Part C · Revision page
Part 0 · Before starting
Words used on this page
- Index: the position of a character, counted from 0. In
"babad", index 0 isb, index 4 isd. - Substring: a continuous piece of the string. You can't jump over characters. In
"babad","aba"(indexes 1..3) is a substring, but"bbd"is not, because it skips the letters in between. - Palindrome: a string that reads the same forwards and backwards, like
"bab","abba"or"racecar". Every single letter is a palindrome on its own. - Pointer: just an integer variable holding an index. "Moving a pointer" means adding or subtracting 1.
What "two pointers" means
The slow way to look at pairs of positions is two nested loops: try every (i, j). That's about n·n/2 pairs, so O(n²). Two pointers is the idea of keeping just two indexes and moving them by a rule, so that at each step you can safely throw away some pairs without checking them. Usually this works because the array is sorted, or has some shape, that tells you which pointer to move. The teacher's point in this batch: sorting is not the real requirement. What you really need is to know which pointer to move, and when.
The family of patterns in this playlist, in one line each:
| shape | how the pointers move | example |
|---|---|---|
| Opposite ends | left starts at 0, right at n−1, they walk towards each other. On a sorted array, if the pair is too small you move left up; if it's too big you move right down. The other choice could only make it worse, so no answer is skipped. | pair sum, Container With Most Water, Valid Palindrome |
| Same direction | a slow "write" pointer and a fast "read" pointer both go left to right. Fast looks at every item, slow marks where the next kept item goes. | Move Zeroes |
| Three pointers | low / mid / high split the array into the 0s, 1s, unknown part and 2s. | Sort Colors |
| Fix one + two pointers | a loop fixes one element, and opposite-end pointers search the rest. Skip equal neighbours to avoid duplicate answers. | 3Sum |
| left-max / right-max | opposite ends, but each side also remembers the tallest bar it has seen, and the side with the smaller max moves. | Trapping Rain Water |
| Both ends of a string | compare s[left] with s[right], then move both inwards. | Valid Palindrome (and Part A here) |
| Expand around a center | both pointers start in the middle and move outwards while the characters match. | Part B of this page, Palindromic Substrings |
This page needs the last two rows. Here they are properly.
Checking a palindrome: reverse copy vs two pointers
The teacher recalls the two ways she taught earlier for checking whether something like "bab" is a palindrome:
- Reverse copy: make a second string that is the first one reversed, then compare them letter by letter. It works, but it needs extra space to hold the reversed copy.
- Two pointers (better): reading the same from both sides means the 1st letter equals the last, the 2nd equals the 2nd-last, and so on. So put
left = 0,right = n − 1. Ifs[left] != s[right], it's not a palindrome, stop right away. If they match, move both inwards (left += 1,right -= 1) and compare the next pair. No extra space.
How many comparisons? The pointers meet in the middle, so a string of length n needs only about n/2 comparisons. Length 3 → 1 comparison. Length 5 → 2 comparisons (the middle letter is never compared). The loop runs while left < right: when they point to the same index there's nothing to compare, and once they cross, every pair is done.
→ The teacher's answer: prefix sums and counting-style tricks are for number problems (sums, frequencies of values). On strings we mostly use two pointers or sliding window. A sliding window is about everything between L and R, the whole block. A palindrome check only compares the two characters at the ends (first with last, then second with second-last). Nothing in between matters for that comparison. "Compare just these two positions, then move" is exactly two pointers. And n/2 comparisons is the least you can do to check a palindrome, so this check can't be made faster.
Expand around a center (the idea used in Part B)
Every palindrome is symmetric around its middle. So instead of starting at the two ends and moving in, you can start at the middle and move out: if the letters just left and just right of the current palindrome are equal, the bigger piece is also a palindrome. Keep going until they differ or you fall off the string.
But where is "the middle"? It depends on the length:
- Odd length (like
"bab","racecar"): the middle is one letter. Start withleft = right = i. - Even length (like
"bb","abba"): there is no middle letter. The middle is the gap between two letters. Start withleft = i,right = i + 1.
Why are there 2n − 1 centers? A string of n letters has n letters and n − 1 gaps between neighbouring letters. Each palindrome has exactly one center, and it's either a letter (odd) or a gap (even). So the possible centers are n + (n − 1) = 2n − 1. For "babad" (n = 5) that's 5 letter-centers + 4 gap-centers = 9:
letters: b a b a d
index: 0 1 2 3 4
centers: o e o e o e o e o
↑ ↑
o = odd center (a letter, L = R = i)
e = even center (the gap between i and i+1, L = i, R = i+1)
5 odd + 4 even = 9 = 2·5 − 1 centers
In code we loop i from 0 to n−1 and try both kinds at every i. At the last i, the "even" call uses i+1 = n, which is outside the string, so it stops at once and finds nothing. That's why we make 2n calls but only 2n − 1 real centers.
Part A · Brute force: every substring + a two-pointer check
LeetCode 5
1The question in simple words
You get a string s. Return the longest substring of s that is a palindrome. You return the substring itself, not its length.
The teacher lists everything for s = "babad", length by length:
| length | substrings | palindromes |
|---|---|---|
| 1 | b, a, b, a, d | all of them (a single letter always reads the same both ways) |
| 2 | ba, ab, ba, ad | none |
| 3 | bab, aba, bad | bab, aba |
| 4 | baba, abad | none |
| 5 | babad | no |
The longest is length 3, and there are two of them: "bab" and "aba". The question doesn't say which one to pick, so either is accepted. Returning whichever you find first is fine.
→ Here, return any of them. But the teacher warns that some versions of this question ask for the lexicographically smallest one (dictionary order). Then you'd have to return
"aba", because "aba" comes before "bab" in the dictionary, and you'd need to compare the strings on ties. Read the question to see if it asks for this.Example 2: s = "cbbd" → answer "bb" (an even-length palindrome, which will matter in Part B).
2What the constraints tell us
- 1 ≤ s.length ≤ 1000, so n ≤ 10³. The string is never empty, so there is always an answer of at least length 1.
- She reads this first because it tells us which complexity will pass. With n = 10³: n² = 10⁶ (easy), n³ = 10⁹ (too much). The usual budget is about 10⁸ simple operations; beyond that you risk TLE (Time Limit Exceeded).
- The characters are digits and English letters, which doesn't change the method.
3Intuition: try every substring, keep the longest palindrome
The simplest plan has two jobs:
- Make every substring (two loops: a start
iand an endj). - Check each one with the two-pointer palindrome check from Part 0. If it's a palindrome and longer than the best one found so far, remember it.
Making every substring with i and j
This is the same way we listed all subarrays in the arrays topic. Fix the start i, then let the end j walk from i to the end of the string:
| start i | j goes i … 4 | substrings made |
|---|---|---|
| 0 | 0, 1, 2, 3, 4 | b, ba, bab, baba, babad |
| 1 | 1, 2, 3, 4 | a, ab, aba, abad |
| 2 | 2, 3, 4 | b, ba, bad |
| 3 | 3, 4 | a, ad |
| 4 | 4 | d |
→ We want the substrings that start at i. The shortest of them is the single letter
s[i], so the end begins at i too. For i = 2 we want "b", "ba", "bad", nothing to the left of index 2. If j started at 0, we'd get "ends before the start", which isn't a substring at all.4Building the logic from the example
Don't build the substring for the check, pass i and j
The check function doesn't need a new string. Give it the whole s plus the two ends i and j, and it uses them directly as left and right. It only compares letters between those two positions.
Is it the longest so far? Use the length j − i + 1
Being a palindrome isn't enough. We want the longest. So we keep the best answer in res (it starts as the empty string, length 0), and when we find a palindrome from i to j, we compare its length with len(res).
The length of the piece from i to j, with both ends included, is j − i + 1. For i = 0, j = 2 ("bab") that's 2 − 0 + 1 = 3.
Cutting out the answer: why j + 1?
Slicing (Java's substring(i, j), Python's s[i:j]) includes the start and excludes the end. We want index j included, so we cut s[i:j + 1]. For i = 0, j = 0 that's s[0:1] = "b", exactly one letter, which is what we want.
Watching it on "babad"
- i = 0, j = 0: "b" is a palindrome, length 1 > 0 →
res = "b". - i = 0, j = 1: "ba", b ≠ a → not a palindrome, skip.
- i = 0, j = 2: "bab" ✓, length 3 > len("b") = 1 →
res = s[0:3] = "bab". - Later, "aba" (i = 1, j = 3) is also a palindrome of length 3, but 3 is not greater than 3, so
resstays "bab". That's why the brute force returns the first longest one it finds.
5Approach steps
res = "".- For every start
ifrom 0 to n−1, and every endjfrom i to n−1: - Check whether
s[i..j]is a palindrome with two pointers moving inwards (left = i,right = j). - If it is, and
j − i + 1 > len(res), setres = s[i:j+1]. - After both loops, return
res.
6Code (Python)
class Solution:
def longestPalindrome(self, s):
res = ""
n = len(s)
for i in range(n): # i = start of the substring
for j in range(i, n): # j = end, begins at i
if self.isPalindrome(s, i, j):
if j - i + 1 > len(res): # longer than the best so far?
res = s[i:j + 1] # j + 1 because the end is excluded
return res
def isPalindrome(self, s, left, right):
while left < right: # stop when they meet or cross
if s[left] != s[right]:
return False # one mismatch is enough
left += 1 # shrink inwards
right -= 1
return True7Code line by line
| line | what it means |
|---|---|
| res = "" | Best palindrome so far. Empty means length 0, so the first palindrome found always beats it. |
| for i in range(n): | Pick where the substring starts. |
| for j in range(i, n): | Pick where it ends. Starting at i gives the one-letter substring first. |
| if self.isPalindrome(s, i, j): | Check only the part from i to j. We pass the indexes, so no new string is made. |
| if j - i + 1 > len(res): | Length of s[i..j] (both ends included). Strictly greater, so on a tie the earlier one stays. |
| res = s[i:j + 1] | Cut out the answer. The slice stops before its end index, so add 1 to include j. |
| while left < right: | Compare pairs until the pointers meet (a single middle letter needs no check) or cross. |
| if s[left] != s[right]: return False | A mismatched pair means it can't read the same both ways. Stop at once. |
| left += 1 right -= 1 | The pair matched, so move to the next pair inwards. |
| return True | Every pair matched, so it's a palindrome. |
8Dry run (hand table)
s = "babad". "pairs compared" lists the (left, right) comparisons that isPalindrome makes.
| i | j | s[i..j] | pairs compared (left, right) | palindrome? | length vs len(res) | res after |
|---|---|---|---|---|---|---|
| 0 | 0 | b | none (left = right) | yes | 1 > 0 → update | b |
| 0 | 1 | ba | (0,1) b≠a | no | – | b |
| 0 | 2 | bab | (0,2) b=b, then left = right | yes | 3 > 1 → update | bab |
| 0 | 3 | baba | (0,3) b≠a | no | – | bab |
| 0 | 4 | babad | (0,4) b≠d | no | – | bab |
| 1 | 1 | a | none | yes | 1 > 3? no | bab |
| 1 | 2 | ab | (1,2) a≠b | no | – | bab |
| 1 | 3 | aba | (1,3) a=a | yes | 3 > 3? no (tie) | bab |
| 1 | 4 | abad | (1,4) a≠d | no | – | bab |
| 2 | 2 | b | none | yes | no | bab |
| 2 | 3 | ba | (2,3) b≠a | no | – | bab |
| 2 | 4 | bad | (2,4) b≠d | no | – | bab |
| 3 | 3 | a | none | yes | no | bab |
| 3 | 4 | ad | (3,4) a≠d | no | – | bab |
| 4 | 4 | d | none | yes | no | bab |
The key moment, i = 0, j = 2 (the check works inwards from both ends):
Final answer: "bab" ✓ (15 substrings checked, which is n(n+1)/2 for n = 5).
9Complexity & remember
- Time O(n³): two nested loops make about n² substrings, and each check costs up to about n/2. The teacher's count for n = 1000: 10³ · 10³ · 500 = 5 × 10⁸ operations. That's above the 10⁸ comfort line, so it may TLE. In her run it was accepted, because each step is a very simple character comparison, but it was very slow.
- Space O(1) extra for the check (we pass indexes). Only
resholds a copy of the best answer. - What can we improve? The palindrome check itself is already as cheap as it can be (n/2). So the waste must be in the two loops that make the substrings. Part B removes one of them.
for i, for j from i → if isPalindrome(s, i, j) and j − i + 1 > len(res) → res = s[i:j+1].The check is two pointers moving inwards while
left < right. Total ≈ n · n · n/2.Part B · Optimal: expand around every center
1The question again, with the new goal
Same input and output: return the longest palindromic substring of s. The goal now is to stop generating substrings with two loops and get from about n³ to about n².
2What the constraints tell us
- n ≤ 1000 → n² = 10⁶. That is far below 10⁸, so an O(n²) solution is comfortable. We don't need anything cleverer for this problem.
- n ≥ 1 → at least one letter, so the answer has length at least 1.
3Intuition: check the palindrome from the inside out
In Part A, to check "bab" we start at the two ends and shrink. The teacher's trick is to do the opposite: start at the middle and expand.
- Put
leftandrighton the middle letter of "bab" (index 1). - Then
left -= 1,right += 1and compare. If they match (b and b), the bigger piece is a palindrome too. If the string were "cbabc", we'd step out again and compare c with c, and so on. - Stop as soon as they differ or a pointer falls off the string.
The win: for each center, one expansion finds the longest palindrome around it, and along the way it has passed through every smaller one around it too. So instead of handing the function a substring (two indexes from two loops), we hand it only the index i and tell it to grow by itself. One loop over the centers replaces the two loops over (i, j).
→ No. Any bigger piece around the same center has that mismatched pair inside it, at mirror positions. A palindrome needs every mirror pair equal, so one broken pair rules out all bigger pieces around this center. Stopping skips nothing.
4Building the logic from examples
Odd and even centers
The teacher points out a catch. For an odd palindrome like "bab", left and right start on the same letter. But for an even palindrome like "aa" or "abba", there is no middle letter, so they must start on two neighbouring letters: left = i, right = i + 1. Then expand the same way. Using "aaaa" as the example: put L at index 1 and R at index 2, they match; step out to 0 and 3, they match, so "aaaa" is a palindrome.
We don't know in advance whether index i is the middle of an odd palindrome, an even one, or both. So for every i we try both: expand(s, i, i) and expand(s, i, i + 1), and keep the longer result. Over all i this covers all 2n − 1 centers from Part 0.
The while condition: three checks, and their order
While building it, the teacher first carried over the shrinking loop's left < right condition, then realised expanding needs something different. When we expand, left goes down and right goes up, so they can run off the ends: left can become −1 and right can become n. So the loop needs:
left >= 0: left is still inside the string.right < len(s): right is still inside the string.s[left] == s[right]: the two letters match.
The two boundary checks must come first. and stops at the first False, so we only read s[left] and s[right] when both are valid indexes.
left <= right from the shrinking version? Do we need it?→ No. When expanding, left starts at or before right and only moves away from it, so
left <= right is always true. It's harmless, but it checks nothing. The real stopping rules are the two boundaries and the mismatch.left >= 0?→ In Java you'd get an index error. In Python it's worse:
s[-1] is allowed and means the last character. The loop would quietly compare against the other end of the string and could report a palindrome that doesn't exist. Always keep the boundary checks.Return a length instead of True
Part A's check returned True or False, and we had i and j to work out the length. Now the function only got one index, so True alone wouldn't tell us how long the palindrome is. So the teacher changes the return value: return the length of the palindrome it grew.
When the loop stops, left and right have gone one step too far. They point at the first bad pair (or outside the string). The palindrome is the part strictly between them. Her example: s = "babad", odd center at i = 0:
- Start L = R = 0: b = b ✓ → L = −1, R = 1.
- L = −1 is outside → stop. The real palindrome is just "b", length 1.
- The usual formula
right − left + 1gives 1 − (−1) + 1 = 3, wrong.right − leftgives 2, still wrong.right − left − 1gives 1, right.
Why minus 1? The palindrome runs from left + 1 to right − 1. Its length is (right − 1) − (left + 1) + 1 = right − left − 1. Her advice: don't memorise formulas. Work them out from where your code leaves the pointers. Here they finish on invalid positions, so the "+1" formula doesn't apply.
Check it on an even center that finds nothing: i = 0, L = 0, R = 1, b ≠ a, so the loop never runs → 1 − 0 − 1 = 0. Correct: no even palindrome there.
Getting the substring back from i and a length
In Part A we had both ends (i and j), so we could cut s[i:j+1]. Now we only have the center i and the length. The palindrome reaches about half its length to the left of i and half to the right. Her example: "bab" in "babad", center i = 1, length 3. Going 1 step left gives index 0, going 1 step right gives index 2, so "bab" is s[0..2].
The exact formulas, which handle odd and even:
start = i - (length - 1) // 2end = i + length // 2then the answer is
s[start:end + 1](length − 1) // 2. Why the −1?→ For odd lengths both give the same answer: length 3 → 3//2 = 1 and (3−1)//2 = 1. For even lengths they differ, and only the −1 version is right. An even palindrome's center is the gap after i, so i itself is the left one of the middle two letters. It has one letter fewer on its left than on its right. Example: "cbbd", even center at i = 1, length 2. The answer "bb" is indexes 1..2.
•
i − length//2 = 1 − 1 = 0 → s[0..2] = "cbb" wrong•
i − (length−1)//2 = 1 − 0 = 1, end = i + length//2 = 1 + 1 = 2 → s[1..2] = "bb" rightSo the left reach is (length−1)//2 and the right reach is length//2. The test file checks "cbbd" for exactly this reason.
Only update when it's longer
We can't overwrite start/end at every i, or a short palindrome found later would replace a longer one found earlier. So first compare. end − start is the "j − i" of the best piece so far, and we update when length > end − start.
end − start is one less than the best length. Is length > end − start correct?→ It never loses the longest one, so the answer is always correct. But it lets an equal length replace the old one, since length 3 > 2 is true when the best is also length 3. That's why this code returns "aba" for "babad" while Part A returned "bab". Both are accepted. If you want the first one found (or need strict control over ties), compare with
length > end - start + 1, which is the real best length.Why save indexes and not the string?
We could keep a res string and cut a new copy every time we find a better palindrome. The teacher instead keeps only the two numbers start and end, and cuts the substring once, at the very end. That avoids making copies inside the loop. start = end = 0 at the beginning means "the first letter", which is always a valid palindrome of length 1.
5Approach steps
start = end = 0.- For every index
i: len1 = expand(s, i, i)(odd, the center is the letter i).len2 = expand(s, i, i + 1)(even, the center is the gap after i).length = max(len1, len2).- If
length > end − start:start = i − (length − 1)//2,end = i + length//2. expand: while inside the string ands[left] == s[right], move left down and right up. Returnright − left − 1.- Return
s[start:end + 1].
6Code (Python)
class Solution:
def longestPalindrome(self, s):
start, end = 0, 0 # best palindrome = s[start..end]
for i in range(len(s)):
len1 = self.expand(s, i, i) # odd: center is the letter i
len2 = self.expand(s, i, i + 1) # even: center is the gap after i
length = max(len1, len2)
if length > end - start:
start = i - (length - 1) // 2
end = i + length // 2
return s[start:end + 1] # cut the string only once
def expand(self, s, left, right):
while left >= 0 and right < len(s) and s[left] == s[right]:
left -= 1 # grow outwards
right += 1
return right - left - 1 # both pointers went one step too far7Code line by line
| line | what it means |
|---|---|
| start, end = 0, 0 | The best palindrome is stored as two indexes. At first it's s[0..0], one letter, which is always a palindrome. |
| for i in range(len(s)): | One loop. Each i is tried as an odd center and as the left side of an even center. |
| len1 = self.expand(s, i, i) | Grow from the single letter i. Gives the longest odd palindrome centred at i (at least 1). |
| len2 = self.expand(s, i, i + 1) | Grow from the pair (i, i+1). Gives the longest even palindrome centred there (0 if s[i] ≠ s[i+1] or i+1 is outside). |
| length = max(len1, len2) | We don't know which kind i belongs to, so keep the longer. |
| if length > end - start: | Only replace the stored answer if this one is long enough (see the tie doubt above). |
| start = i - (length - 1) // 2 end = i + length // 2 | Turn "center i + length" into the two ends. The −1 on the left handles even lengths. |
| return s[start:end + 1] | Cut the answer once. +1 because the slice end is excluded. |
| while left >= 0 and right < len(s) and s[left] == s[right]: | Boundaries first, then compare. Keep growing while the new outer pair matches. |
| left -= 1 right += 1 | Expanding: the opposite direction of Part A's check. |
| return right - left - 1 | The palindrome is s[left+1 .. right−1], so its length is right − left − 1. |
8Dry run
s = "babad". Each row is one call of expand. "grows" lists the pairs it compared.
| step | i | center | start L, R | pairs compared (L,R) → result | stops at L, R | length | decision | best after (start..end) |
|---|---|---|---|---|---|---|---|---|
| 1 | 0 | odd "b" | 0, 0 | (0,0) b=b ✓ → (−1,1) outside | −1, 1 | 1 | max = 1 > 0 − 0 → update: start 0, end 0 | 0..0 "b" |
| 2 | 0 | even b|a | 0, 1 | (0,1) b≠a ✗ | 0, 1 | 0 | ||
| 3 | 1 | odd "a" | 1, 1 | (1,1) ✓ → (0,2) b=b ✓ → (−1,3) outside | −1, 3 | 3 | 3 > 0 → start = 1 − 1 = 0, end = 1 + 1 = 2 | 0..2 "bab" |
| 4 | 1 | even a|b | 1, 2 | (1,2) a≠b ✗ | 1, 2 | 0 | ||
| 5 | 2 | odd "b" | 2, 2 | (2,2) ✓ → (1,3) a=a ✓ → (0,4) b≠d ✗ | 0, 4 | 3 | 3 > 2 − 0 = 2 → tie replaces: start 1, end 3 | 1..3 "aba" |
| 6 | 2 | even b|a | 2, 3 | (2,3) b≠a ✗ | 2, 3 | 0 | ||
| 7 | 3 | odd "a" | 3, 3 | (3,3) ✓ → (2,4) b≠d ✗ | 2, 4 | 1 | 1 > 2? no | 1..3 "aba" |
| 8 | 3 | even a|d | 3, 4 | (3,4) a≠d ✗ | 3, 4 | 0 | ||
| 9 | 4 | odd "d" | 4, 4 | (4,4) ✓ → (3,5) outside | 3, 5 | 1 | 1 > 2? no | 1..3 "aba" |
| 10 | 4 | even d|? | 4, 5 | R = 5 is outside at once (no 10th center) | 4, 5 | 0 |
The string at the key moments (yellow = the pair being compared, grey = already inside the palindrome):
How far each of the 9 centers grows (the picture to keep in your head):
index: 0 1 2 3 4 s: b a b a d odd @0 b → 1 even @0|1 ✗ → 0 odd @1 [b a b] → 3 "bab" even @1|2 ✗ → 0 odd @2 [a b a] → 3 "aba" even @2|3 ✗ → 0 odd @3 a → 1 (b ≠ d stops it) even @3|4 ✗ → 0 odd @4 d → 1
Final answer: "aba" ✓, a valid longest palindrome (length 3). As explained in the tie doubt, the brute force gave "bab". Both are accepted.
Second check, s = "cbbd": at i = 1 the even center (1, 2) b = b ✓ grows to (0, 3) c ≠ d ✗ → length 3 − 0 − 1 = 2 → start = 1 − 0 = 1, end = 1 + 1 = 2 → "bb" ✓.
9Complexity & remember
- Time O(n²): one loop over the n positions (2n − 1 centers). Each expansion can grow at most about n/2 steps, the same cost as the shrinking check in Part A, just in the other direction. So n · n/2 → O(n²). We saved a whole factor of n by not generating substrings.
- Space O(1): a few integers. The answer is cut only once at the end.
- On LeetCode it ran much faster than the brute force.
- Can it be even faster? Yes. Manacher's algorithm solves this in O(n). The teacher calls it advanced and leaves it for a separate video. For interviews, expand-around-center is the expected answer.
expand(i, i) and expand(i, i+1) → 2n − 1 centers.expand:
while left ≥ 0 and right < n and s[left] == s[right]: left −= 1, right += 1 → return right − left − 1.ends:
start = i − (len−1)//2, end = i + len//2, answer s[start:end+1].Part C · Revision page
| Brute force (Part A) | Expand around center (Part B) | |
|---|---|---|
| how substrings are found | two loops: every (i, j) | one loop: every center, grown outwards |
| pointer direction | ends → middle (shrink) | middle → ends (expand) |
| check function returns | True / False | the length found |
| loop condition | left < right | left ≥ 0 and right < n and s[left] == s[right] |
| length formula | j − i + 1 (both ends valid) | right − left − 1 (both ends one step too far) |
| stored answer | the string res | two indexes, cut once at the end |
| "babad" gives | "bab" (first longest) | "aba" (a tie replaces) |
| time / space | O(n³) ≈ 5·10⁸ for n = 1000 / O(1) | O(n²) / O(1) |
| odd center | even center | |
|---|---|---|
| where | a letter | the gap between two letters |
| start | left = right = i | left = i, right = i + 1 |
| how many | n | n − 1 |
| smallest result | 1 (the letter itself) | 0 (if the two letters differ) |
| example | "bab", "racecar" | "bb", "abba" |
2. Grow from the middle: try each i as an odd center (i, i) and an even center (i, i+1). That's 2n − 1 centers.
3. Expand while inside the string and the letters match. Check the boundaries first.
4. Return
right − left − 1, because the pointers stop one step outside the palindrome.5.
start = i − (len−1)//2, end = i + len//2, return s[start:end+1].✗ comparing letters before the boundary checks (and in Python,
s[-1] silently reads the last letter)✗ returning
right − left + 1 from expand✗
start = i − len//2 (wrong for even lengths)✗ updating start/end without comparing lengths first
✗ forgetting
+ 1 in s[start:end + 1]✗ expecting one exact string when there's a tie (any longest one is accepted)
s = Solution()
print(s.longestPalindrome("babad")) # "bab" or "aba"
print(s.longestPalindrome("cbbd")) # "bb" (even center)
print(s.longestPalindrome("a")) # "a"
print(s.longestPalindrome("ac")) # "a" (any single letter)
print(s.longestPalindrome("aaaa")) # "aaaa" (all the same letter)
print(s.longestPalindrome("forgeeksskeegfor")) # "geeksskeeg"Based on this video: Longest Palindromic Substring | Two Pointers