Longest Palindromic Substring
MedPrompt
Given a string s, return the longest palindromic substring in s.
Example 1:
Input: s = "babad"
Output: "bab"
Explanation: "aba" is also a valid answer.Example 2:
Input: s = "cbbd"
Output: "bb"
Constraints:
1 <= s.length <= 1000sconsist of only digits and English letters.
Approaches
4 approaches with complexity analysis and trade-offs.
This approach is the most straightforward but also the most inefficient. The core idea is to check every single substring of the input string s and determine if it's a palindrome. We maintain a variable to keep track of the longest palindromic substring found so far.
Algorithm
- Initialize
maxLength = 1andstart = 0. - Iterate through the string with a starting index
ifrom 0 ton-1. - For each
i, iterate with an ending indexjfromiton-1. - Extract the substring
subfromitoj. - Check if
subis a palindrome. This check takes O(length of sub) time. - If
subis a palindrome and its length is greater thanmaxLength, updatemaxLengthandstart. - After all substrings are checked, return the substring starting at
startwith lengthmaxLength.
Walkthrough
This approach is the most straightforward but also the most inefficient. The core idea is to check every single substring of the input string s and determine if it's a palindrome. We maintain a variable to keep track of the longest palindromic substring found so far.
Algorithm
- Initialize variables
maxLengthto 1 andstartto 0 to store the length and starting index of the longest palindrome. - Use nested loops to generate all possible substrings. The outer loop (with index
i) selects the starting character, and the inner loop (with indexj) selects the ending character. - For each substring
s[i...j], call a helper functionisPalindrometo check if it's a palindrome. - The
isPalindromecheck is done by comparing characters from both ends of the substring, moving inwards. This takes time proportional to the length of the substring. - If the current substring is a palindrome and its length is greater than
maxLength, updatemaxLengthandstart. - After checking all substrings, the longest palindromic substring is
s.substring(start, start + maxLength).
Code
class Solution { public String longestPalindrome(String s) { if (s == null || s.length() < 1) return ""; int n = s.length(); int start = 0; int maxLength = 1; for (int i = 0; i < n; i++) { for (int j = i; j < n; j++) { int len = j - i + 1; if (len > maxLength && isPalindrome(s, i, j)) { maxLength = len; start = i; } } } return s.substring(start, start + maxLength); } private boolean isPalindrome(String s, int low, int high) { while (low < high) { if (s.charAt(low++) != s.charAt(high--)) { return false; } } return true; }}Complexity
Time
O(n^3)
Space
O(1)
Trade-offs
Pros
Simple to understand and implement.
Requires no extra space (O(1) auxiliary space).
Cons
Extremely inefficient with a time complexity of O(n^3).
Will likely result in a 'Time Limit Exceeded' error for larger inputs (e.g., n=1000).
This approach improves upon the brute-force method by avoiding re-computation. We use a 2D table, dp[n][n], where dp[i][j] stores whether the substring s[i...j] is a palindrome. The state dp[i][j] depends on the state dp[i+1][j-1].
Algorithm
- Create a boolean table
dp[n][n]. - Initialize
maxLength = 1andstart = 0. - All substrings of length 1 are palindromes, so set
dp[i][i] = truefor alli. - Check for substrings of length 2. If
s[i] == s[i+1], thendp[i][i+1] = true. UpdatemaxLengthandstart. - Iterate for lengths
kfrom 3 ton. - For each length
k, iterate through all possible starting indicesi. The ending indexjwill bei + k - 1. - The substring
s[i...j]is a palindrome ifs[i] == s[j]and the inner substrings[i+1...j-1]is also a palindrome (i.e.,dp[i+1][j-1]is true). - If
dp[i][j]is true, updatemaxLengthandstart. - After filling the table, return the substring from
startwithmaxLength.
Walkthrough
This approach improves upon the brute-force method by avoiding re-computation. We use a 2D table, dp[n][n], where dp[i][j] stores whether the substring s[i...j] is a palindrome. The state dp[i][j] depends on the state dp[i+1][j-1].
Algorithm
- Create a 2D boolean array
dpof sizen x n.dp[i][j]will betrueifs[i...j]is a palindrome. - Initialize
startandmaxLengthto track the longest palindrome. - Base Case 1 (Length 1): All substrings of length 1 are palindromes. So, set
dp[i][i] = truefor alli. - Base Case 2 (Length 2): Check all substrings of length 2. If
s.charAt(i) == s.charAt(i+1), setdp[i][i+1] = trueand updatemaxLengthandstart. - General Case (Length > 2): Iterate for substring lengths
kfrom 3 ton. For each length, iterate through all possible start indicesi. The end indexjisi + k - 1. - A substring
s[i...j]is a palindrome if the outer characters match (s.charAt(i) == s.charAt(j)) AND the inner substrings[i+1...j-1]is a palindrome (dp[i+1][j-1] == true). - If
dp[i][j]becomes true, check if its lengthkis the new maximum length and updatestartandmaxLengthaccordingly. - Finally, return the substring identified by
startandmaxLength.
Code
class Solution { public String longestPalindrome(String s) { if (s == null || s.length() < 1) return ""; int n = s.length(); boolean[][] dp = new boolean[n][n]; int start = 0; int maxLength = 1; for (int i = 0; i < n; i++) { dp[i][i] = true; } for (int i = 0; i < n - 1; i++) { if (s.charAt(i) == s.charAt(i + 1)) { dp[i][i + 1] = true; start = i; maxLength = 2; } } for (int k = 3; k <= n; k++) { for (int i = 0; i < n - k + 1; i++) { int j = i + k - 1; if (dp[i + 1][j - 1] && s.charAt(i) == s.charAt(j)) { dp[i][j] = true; if (k > maxLength) { start = i; maxLength = k; } } } } return s.substring(start, start + maxLength); }}Complexity
Time
O(n^2)
Space
O(n^2)
Trade-offs
Pros
Much more efficient than brute force.
Guaranteed to pass within time limits for typical constraints (n <= 1000).
Cons
Requires O(n^2) extra space for the DP table, which can be large for n=1000.
This approach is an elegant and efficient way to solve the problem in O(n^2) time with O(1) space. Instead of checking every substring, we observe that a palindrome is symmetric around its center. A center can be a single character (for odd-length palindromes) or the space between two characters (for even-length palindromes). There are 2n - 1 such potential centers.
Algorithm
- Initialize
start = 0andend = 0to store the boundaries of the longest palindrome found. - Iterate through the string from
i = 0ton-1. - For each
i, consider it as the center of a potential palindrome. There are two cases:- Odd length palindrome: The center is the character
s[i]. Expand from this center. - Even length palindrome: The center is between
s[i]ands[i+1]. Expand from this center.
- Odd length palindrome: The center is the character
- Create a helper function
expand(s, left, right)that expands outwards (left--,right++) as long asleft >= 0,right < n, ands.charAt(left) == s.charAt(right). It returns the length of the palindrome found. - For each
i, callexpand(s, i, i)andexpand(s, i, i+1). - Let
lenbe the maximum of the two lengths returned. - If
lenis greater than the current longest palindrome's length (end - start), updatestartandend. - After the loop, return
s.substring(start, end + 1).
Walkthrough
This approach is an elegant and efficient way to solve the problem in O(n^2) time with O(1) space. Instead of checking every substring, we observe that a palindrome is symmetric around its center. A center can be a single character (for odd-length palindromes) or the space between two characters (for even-length palindromes). There are 2n - 1 such potential centers.
Algorithm
- Initialize
startandendindices to track the boundaries of the longest palindrome found so far. - Iterate through the string with an index
ifrom 0 ton-1. - For each
i, treat it as a potential center and expand outwards to find the longest palindrome. We need to check two cases:- Odd-length palindrome: The center is the character
s[i]. We find its length by calling a helper functionexpandAroundCenter(s, i, i). - Even-length palindrome: The center is between
s[i]ands[i+1]. We find its length by callingexpandAroundCenter(s, i, i + 1).
- Odd-length palindrome: The center is the character
- The
expandAroundCenterhelper function takes two pointers,leftandright, and expands them (left--,right++) as long as they are within the string bounds and the characters at these pointers are equal. It returns the length of the palindrome found (right - left - 1). - Compare the lengths from the odd and even cases and take the maximum.
- If this maximum length is greater than the current longest palindrome's length (
end - start), updatestartandendto reflect the new longest palindrome's boundaries. - After iterating through all possible centers, return
s.substring(start, end + 1).
Code
class Solution { public String longestPalindrome(String s) { if (s == null || s.length() < 1) return ""; int start = 0, end = 0; for (int i = 0; i < s.length(); i++) { int len1 = expandAroundCenter(s, i, i); int len2 = expandAroundCenter(s, i, i + 1); int len = Math.max(len1, len2); if (len > end - start) { start = i - (len - 1) / 2; end = i + len / 2; } } return s.substring(start, end + 1); } private int expandAroundCenter(String s, int left, int right) { while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) { left--; right++; } return right - left - 1; }}Complexity
Time
O(n^2)
Space
O(1)
Trade-offs
Pros
Same time complexity as DP but with O(1) space complexity, making it more memory-efficient.
Often faster in practice than the DP solution due to less overhead and better cache performance.
Cons
Still has a quadratic time complexity, which is not optimal.
Manacher's algorithm is a clever and optimal linear-time solution. It avoids redundant computations by reusing information about previously found palindromes. The core idea is to transform the input string to handle both odd and even length palindromes uniformly and then use an auxiliary array to store the palindrome radius for each center.
Algorithm
- Transform the string: Insert a special character (e.g., '#') between each character of
sand at the ends. Fors = "aba", it becomesT = "#a#b#a#". This ensures all palindromes have an odd length. - Create an array
Pof the same length as the transformed stringT.P[i]will store the radius of the palindrome centered atT[i]. - Initialize
center = 0andright = 0.centeris the center of the palindrome that extends furthest to the right, andrightis its right boundary. - Iterate through the transformed string
Tfromi = 1tolen(T)-1. - For each
i, find its mirror indexi_mirror = 2 * center - i. - Optimization: If
iis within the current right boundary (right > i), we can initializeP[i]tomin(right - i, P[i_mirror]). - Expand: Attempt to expand the palindrome centered at
iby incrementingP[i]as long as the characters atT[i + 1 + P[i]]andT[i - 1 - P[i]]match. - Update center and right boundary: If the palindrome centered at
iexpands beyond the currentrightboundary, updatecentertoiandrighttoi + P[i]. - Find the maximum value in the
Parray,maxLen, and its center,maxCenter. - Convert the result back to the original string's coordinates and return the substring.
Walkthrough
Manacher's algorithm provides a linear-time solution, making it the most efficient approach. It achieves this by cleverly reusing information from previously computed palindromes to avoid redundant character comparisons.
Algorithm
- Transform String: First, transform the input string
sto handle odd and even length palindromes uniformly. Insert a special character (e.g., '#') between each character and at the ends. For example,s = "babad"becomesT = "^#b#a#b#a#d#$". The sentinels^and$prevent out-of-bounds checks during expansion. - Palindrome Radii Array: Create an array
Pof the same size as the transformed stringT.P[i]will store the radius of the palindrome centered atT[i]. - Iterate and Expand: Maintain a
center(C) and arightboundary (R) of the palindrome found so far that extends furthest to the right. Iterate throughTwith indexi. - Key Optimization: For the current position
i, find its mirror positioni_mirror = 2*C - iwith respect to the current centerC. Ifiis within the right boundaryR(i.e.,R > i), we can initializeP[i]with a guaranteed minimum radius ofmin(R - i, P[i_mirror]). This avoids re-checking characters that are already known to be part of a palindrome. - Expand: From this initialized radius, attempt to expand the palindrome centered at
iby comparing charactersT[i + 1 + P[i]]andT[i - 1 - P[i]]. - Update Center/Right Boundary: If the palindrome at
iexpands beyond the currentR, updateCtoiandRtoi + P[i]. - Find Longest: After iterating through
T, find the maximum valuemaxLenin theParray and its corresponding centercenterIndex. - Convert Back: Use
maxLenandcenterIndexto calculate the start index and length of the longest palindrome in the original stringsand return the substring.
Code
class Solution { public String longestPalindrome(String s) { if (s == null || s.length() < 1) return ""; StringBuilder tBuilder = new StringBuilder("^"); for (char c : s.toCharArray()) { tBuilder.append('#').append(c); } tBuilder.append("#$"); String T = tBuilder.toString(); int n = T.length(); int[] P = new int[n]; int C = 0, R = 0; for (int i = 1; i < n - 1; i++) { int i_mirror = 2 * C - i; if (R > i) { P[i] = Math.min(R - i, P[i_mirror]); } else { P[i] = 0; } while (T.charAt(i + 1 + P[i]) == T.charAt(i - 1 - P[i])) { P[i]++; } if (i + P[i] > R) { C = i; R = i + P[i]; } } int maxLen = 0; int centerIndex = 0; for (int i = 1; i < n - 1; i++) { if (P[i] > maxLen) { maxLen = P[i]; centerIndex = i; } } int start = (centerIndex - maxLen) / 2; return s.substring(start, start + maxLen); }}Complexity
Time
O(n)
Space
O(n)
Trade-offs
Pros
Optimal time complexity of O(n).
Each character is compared a constant number of times on average.
Cons
Significantly more complex to understand and implement correctly compared to other approaches.
The logic, especially the use of the mirror index and right boundary, is not intuitive.
Solutions
Solution
public class Solution { public string LongestPalindrome(string s) { int n = s.Length; bool[, ] f = new bool[n, n]; for (int i = 0; i < n; i++) { for (int j = 0; j < n; ++j) { f[i, j] = true; } } int k = 0, mx = 1; for (int i = n - 2; i >= 0; --i) { for (int j = i + 1; j < n; ++j) { f[i, j] = false; if (s[i] == s[j]) { f[i, j] = f[i + 1, j - 1]; if (f[i, j] && mx < j - i + 1) { mx = j - i + 1; k = i; } } } } return s.Substring(k, mx); }}class Solution {public String longestPalindrome(String s) { int n = s.length(); boolean[][] f = new boolean[n][n]; for (var g : f) { Arrays.fill(g, true); } int k = 0, mx = 1; for (int i = n - 2; i >= 0; --i) { for (int j = i + 1; j < n; ++j) { f[i][j] = false; if (s.charAt(i) == s.charAt(j)) { f[i][j] = f[i + 1][j - 1]; if (f[i][j] && mx < j - i + 1) { mx = j - i + 1; k = i; } } } } return s.substring(k, k + mx); }}/** * @param {string} s * @return {string} */ var longestPalindrome = function ( s,) { const n = s.length; const f = Array(n) .fill(0) .map(() => Array(n).fill(true)); let k = 0; let mx = 1; for (let i = n - 2; i >= 0; --i) { for (let j = i + 1; j < n; ++j) { f[i][j] = false; if (s[i] === s[j]) { f[i][j] = f[i + 1][j - 1]; if (f[i][j] && mx < j - i + 1) { mx = j - i + 1; k = i; } } } } return s.slice(k, k + mx);};class Solution {public: string longestPalindrome(string s) { int n = s.size(); vector<vector<bool>> f(n, vector<bool>(n, true)); int k = 0, mx = 1; for (int i = n - 2; ~i; --i) { for (int j = i + 1; j < n; ++j) { f[i][j] = false; if (s[i] == s[j]) { f[i][j] = f[i + 1][j - 1]; if (f[i][j] && mx < j - i + 1) { mx = j - i + 1; k = i; } } } } return s.substr(k, mx); }};class Solution: # class Solution : def longestPalindrome ( self , s : str ) -> str : n = len ( s ) f = [[ True ] * n for _ in range ( n )] k , mx = 0 , 1 for i in range ( n - 2 , - 1 , - 1 ): for j in range ( i + 1 , n ): f [ i ][ j ] = False if s [ i ] == s [ j ]: f [ i ][ j ] = f [ i + 1 ][ j - 1 ] if f [ i ][ j ] and mx < j - i + 1 : k , mx = i , j - i + 1 return s [ k : k + mx ] def longestPalindrome(self, s: str) -> str: mlen = 0 start = end = 0 n = len(s) dp = [[False] * n for i in range(n)] for j in range(n): for i in range(j + 1): dp[i][j] = (i == j) or (s[i] == s[j] and j - i == 1) or (s[i] == s[j] and dp[i + 1][j - 1]) if dp[i][j] is True and j - i + 1 > mlen: mlen = j - i + 1 start = i end = j return s[start: end + 1]Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.