5. Longest Palindromic Substring - Explanation
Description
Given a string s, return the longest substring of s that is a palindrome.
A palindrome is a string that reads the same forward and backward.
If there are multiple palindromic substrings that have the same length, return any one of them.
Example 1:
Input: s = "ababd"
Output: "bab"Explanation: Both "aba" and "bab" are valid answers.
Example 2:
Input: s = "abbc"
Output: "bb"Constraints:
1 <= s.length <= 1000scontains only digits and English letters.
Topics
Recommended Time & Space Complexity
You should aim for a solution as good or better than O(n^2) time and O(1) space, where n is the length of the given string.
Hint 1
A brute-force solution would be to check if every substring is a palindrome and return the maximum length among all the palindromic substring lengths. This would be an O(n^3) time solution. Can you think of a better way? Perhaps you should consider thinking in terms of the center of a palindrome.
Hint 2
Iterate over the string with index i and treat the current character as the center. For this character, try to extend outward to the left and right simultaneously, but only if both characters are equal. Update the result variable accordingly. How would you implement this? Can you consider both cases: even-length and odd-length palindromes?
Hint 3
Maintain two variables, resLen and res, which denote the length of the longest palindrome and the start index of that palindrome, respectively. At each index, you can create an odd-length palindrome starting at that index extending outward from both its left and right indices, i.e., i - 1 and i + 1. How can you find the even-length palindrome for this index?
Hint 4
For an even-length palindrome, consider expanding from indices i and i + 1. This two-pointer approach, extending from the center of the palindrome, will help find all palindromic substrings in the given string. Update the two result variables and return the substring starting at res with a length of resLen.
Prerequisites
Before attempting this problem, you should be comfortable with:
- Two Pointers - Used to expand around centers and verify palindromes by comparing characters from both ends
- Dynamic Programming (2D) - The DP solution uses a table where
dp[i][j]indicates if the substring from index i to j is a palindrome - String Manipulation - Understanding substrings, character indexing, and string slicing
- Manacher's Algorithm (Advanced) - Optional but provides the optimal O(n) solution by reusing palindrome information
1. Brute Force
Intuition
A palindrome reads the same forward and backward.
The simplest idea is to try every possible substring and check whether it is a palindrome, then keep the longest one found.
For each pair (i, j):
- Assume
s[i:j]is a candidate substring - Use two pointers (
landr) to check if it’s a palindrome - If valid and longer than the current answer, update the result
This approach is straightforward but inefficient.
Algorithm
- Initialize an empty result string and length
0. - For every start index
i:- For every end index
j >= i:- Check if substring
s[i..j]is a palindrome using two pointers. - If it is a palindrome and longer than the current best:
- Update the result.
- Check if substring
- For every end index
- Return the longest palindrome found.
class Solution:
def longestPalindrome(self, s: str) -> str:
res, resLen = "", 0
for i in range(len(s)):
for j in range(i, len(s)):
l, r = i, j
while l < r and s[l] == s[r]:
l += 1
r -= 1
if l >= r and resLen < (j - i + 1):
res = s[i : j + 1]
resLen = j - i + 1
return resTime & Space Complexity
- Time complexity:
- Space complexity:
2. Dynamic Programming
Intuition
Instead of re-checking the same substrings again and again, we remember whether a substring is a palindrome.
Let:
dp[i][j] = trueif the substrings[i..j]is a palindrome.
A substring s[i..j] is a palindrome when:
- The end characters match:
s[i] == s[j] - And the inside part is also a palindrome:
dp[i+1][j-1]- Special small cases: if the length is
1,2, or3(j - i <= 2), then matching ends is enough because the middle is empty or a single char.
- Special small cases: if the length is
We fill dp from bottom to top (i from n-1 down to 0) so that when we compute dp[i][j], the value dp[i+1][j-1] is already known.
While filling, we keep track of the best (longest) palindrome seen so far.
Algorithm
- Let
n = len(s). Create a 2D tabledp[n][n]initialized tofalse. - Keep
resIdx = 0andresLen = 0for the best answer. - For
ifromn-1down to0:- For
jfromiup ton-1:- If
s[i] == s[j]and (j - i <= 2ORdp[i+1][j-1]istrue):- Mark
dp[i][j] = true - If
(j - i + 1)is bigger thanresLen, updateresIdxandresLen.
- Mark
- If
- For
- Return
s[resIdx : resIdx + resLen].
class Solution:
def longestPalindrome(self, s: str) -> str:
resIdx, resLen = 0, 0
n = len(s)
dp = [[False] * n for _ in range(n)]
for i in range(n - 1, -1, -1):
for j in range(i, n):
if s[i] == s[j] and (j - i <= 2 or dp[i + 1][j - 1]):
dp[i][j] = True
if resLen < (j - i + 1):
resIdx = i
resLen = j - i + 1
return s[resIdx : resIdx + resLen]Time & Space Complexity
- Time complexity:
- Space complexity:
3. Two Pointers
Intuition
A palindrome expands symmetrically from its center.
Every palindrome has one of two centers:
- Odd length → a single character center (e.g.
"racecar") - Even length → between two characters (e.g.
"abba")
So instead of checking all substrings, we:
- Treat every index as a possible center
- Expand left and right while characters match
- Track the longest palindrome found during expansion
This avoids extra space and redundant checks.
Algorithm
- Initialize:
resIdx = 0- starting index of best palindromeresLen = 0- length of best palindrome
- For each index
iin the string:- Odd-length palindrome
- Set
l = i,r = i - Expand while
l >= 0,r < n, ands[l] == s[r]
- Set
- Even-length palindrome
- Set
l = i,r = i + 1 - Expand while
l >= 0,r < n, ands[l] == s[r]
- Set
- During each expansion, update
resIdxandresLenif a longer palindrome is found
- Odd-length palindrome
- Return substring
s[resIdx : resIdx + resLen]
class Solution:
def longestPalindrome(self, s: str) -> str:
resIdx = 0
resLen = 0
for i in range(len(s)):
# odd length
l, r = i, i
while l >= 0 and r < len(s) and s[l] == s[r]:
if (r - l + 1) > resLen:
resIdx = l
resLen = r - l + 1
l -= 1
r += 1
# even length
l, r = i, i + 1
while l >= 0 and r < len(s) and s[l] == s[r]:
if (r - l + 1) > resLen:
resIdx = l
resLen = r - l + 1
l -= 1
r += 1
return s[resIdx : resIdx + resLen]Time & Space Complexity
- Time complexity:
- Space complexity:
- extra space.
- space for the output string.
4. Manacher's Algorithm
Intuition
Manacher’s Algorithm is an optimized way to find the longest palindromic substring in linear time.
The key ideas are:
- Unify odd and even length palindromes by inserting a special character (like
#) between characters.- Example:
"abba"→"#a#b#b#a#"
- Example:
- Use previous palindrome information to avoid re-checking characters.
- Maintain a current rightmost palindrome and mirror indices to reuse results.
Instead of expanding from every center independently, Manacher’s algorithm reuses symmetry, making it much faster than the two-pointer approach.
Algorithm
- Transform the string
- Insert
#between characters and at both ends to handle odd/even palindromes uniformly.
- Insert
- Create an array
p[]p[i]= radius of palindrome centered at indexiin the transformed string.
- Maintain two pointers:
center- center of the current rightmost palindromeright- right boundary of that palindrome
- For each index
i:- If
iis inside the current palindrome:- Initialize
p[i]using its mirror aroundcenter
- Initialize
- Expand around
iwhile characters match - If the palindrome expands beyond
right, updatecenterandright
- If
- After processing:
- Find the index with the maximum value in
p - Convert that position back to the original string indices
- Find the index with the maximum value in
- Return the longest palindromic substring
class Solution:
def longestPalindrome(self, s: str) -> str:
def manacher(s):
t = '#' + '#'.join(s) + '#'
n = len(t)
p = [0] * n
l, r = 0, 0
for i in range(n):
p[i] = min(r - i, p[l + (r - i)]) if i < r else 0
while (i + p[i] + 1 < n and i - p[i] - 1 >= 0
and t[i + p[i] + 1] == t[i - p[i] - 1]):
p[i] += 1
if i + p[i] > r:
l, r = i - p[i], i + p[i]
return p
p = manacher(s)
resLen, center_idx = max((v, i) for i, v in enumerate(p))
resIdx = (center_idx - resLen) // 2
return s[resIdx : resIdx + resLen]Time & Space Complexity
- Time complexity:
- Space complexity:
Common Pitfalls
Handling Both Odd and Even Length Palindromes
When expanding around centers, you must check both odd-length palindromes (single character center like "aba") and even-length palindromes (between two characters like "abba"). Forgetting to handle one case means missing valid palindromes. Always expand from both (i, i) and (i, i+1) for each position.
Off-by-One Errors in Substring Extraction
After finding the palindrome boundaries, extracting the correct substring is error-prone. If your left pointer l and right pointer r point to positions just outside the palindrome after expansion, you need to adjust them (e.g., l+1 to r-1) before extracting. Verify your indices with simple test cases like "a" and "aa".
Returning Wrong Result for Single Character Strings
For an input like "a", the longest palindromic substring is "a" itself. If you initialize your result string as empty and only update it when you find a longer palindrome, you might return an empty string for single-character inputs. Ensure your initialization handles this edge case correctly.
Sign in to join the discussion