Longest Palindromic Substring
Problem
Given a string s, return the longest palindromic substring in s.
Incase of conflict, return the substring which occurs first ( with the least starting index ).
Substring of string S: S[i...j] where 0 <= i <= j < len(S)
Examples
Example 1:
Input: s = "babad"
Output: "bab"
Explanation: "aba" is also a valid answer.
Example 2:
Input: s = "cbbd"
Output: "bb"
Solution
Video explanation
Here is the video explaining below methods in detail. Please check it out:
<div class="youtube-embed"><iframe src="https://www.youtube.com/embed/IqsfvNg6oyA" frameborder="0" allowfullscreen></iframe></div>
Method 1 - Brute Force
The simple approach is to check each substring whether the substring is a palindrome or not. We can run three loops, the outer two loops pick all substrings one by one by fixing the corner characters, the inner loop checks whether the picked substring is palindrome or not.
Code
Java
class Solution {
public String longestPalindrome(String s) {
String ans = "";
int maxLen = 0;
for (int i = 0; i < s.length(); i++) {
for (int j = i; j < s.length(); j++) {
// Extract substring s[i...j]
String sub = s.substring(i, j + 1);
// Check if substring is palindrome
if (isPalindrome(sub) && sub.length() > maxLen) {
ans = sub;
maxLen = sub.length();
}
}
}
return ans;
}
private boolean isPalindrome(String s) {
int l = 0, r = s.length() - 1;
while (l < r) {
if (s.charAt(l) != s.charAt(r)) {
return false;
}
l++;
r--;
}
return true;
}
}
Python
class Solution:
def longestPalindrome(self, s: str) -> str:
ans: str = ""
max_len: int = 0
for i in range(len(s)):
for j in range(i, len(s)):
# Extract substring s[i...j]
sub: str = s[i:j + 1]
# Check if substring is palindrome
if self.is_palindrome(sub) and len(sub) > max_len:
ans = sub
max_len = len(sub)
return ans
def is_palindrome(self, s: str) -> bool:
l: int = 0
r: int = len(s) - 1
while l < r:
if s[l] != s[r]:
return False
l += 1
r -= 1
return True
Complexity
- ⏰ Time complexity:
O(n^3)- Generating all substrings involves two nested loops:
O(n^2). - For each substring, checking if it’s a palindrome takes
O(k)(wherekis the length of the substring). - Worst-case complexity:
O(n^3).
- Generating all substrings involves two nested loops:
- 🧺 Space complexity:
O(1)
Method 2 - Dynamic Programming
Let s be the input string, i and j are two indices of the string. Define a 2-dimension array "table" and let dp[i][j] denote whether a substring from j to i is palindrome, and it is filled in bottom up manner.
The value of dp[i][j] is true, if the substring is palindrome, otherwise false.
dp[i][j] = true if (dp[i+1][j-1] && ( s.charAt(i)==s.charAt(j) ))
Code
Java
public String longestPalindrome(String s) {
if (s == null || s.length() <= 1) {
return s;
}
int n = s.length();
boolean[][] dp = new boolean[n][n];
int maxLen = 1; // atleast longest palindrome will have 1 char
int start = 0;
int end = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j <= i; j++) {
// Base cases:
// - Single character is a palindrome (length 1)
if (i == j) {
dp[i][j] = true;
}
// - Two adjacent characters are a palindrome (length 2)
else if (i - j == 1 && s.charAt(i) == s.charAt(j)) {
dp[i][j] = true;
} else {
// Check if the substring between (j + 1) and (i - 1) is a palindrome
// and the characters at i and j are the same
dp[i][j] = s.charAt(i) == s.charAt(j) && dp[j + 1][i - 1];
}
// Update maxLen and start/end if a longer palindrome is found
if (dp[i][j] && maxLen < i - j + 1) {
maxLen = i - j + 1;
start = j;
end = i;
}
}
}
return s.substring(start, end + 1);;
}
Complexity
- ⏰ Time complexity:
O(n^2) - 🧺 Space complexity:
O(n^2)
Dry Run
Consider the string s = "babad" (n = 5). Lets start filling the DP table.
Filling in DP table
For the base case 1, aka i == j, we will have DP table like:
| b | a | b | a | d | |
|---|---|---|---|---|---|
| b | |||||
| a | |||||
| b | |||||
| a | |||||
| d |
i = 0, j = 0,i == j⇨dp[i][j] = T
| b | a | b | a | d | |
|---|---|---|---|---|---|
| b | T | ||||
| a | |||||
| b | |||||
| a | |||||
| d |
i = 1, j = 0. Asi - j == 1, that means adjacent characters, butb != ahence we go to last case.dp[i][j] = F.
| b | a | b | a | d | |
|---|---|---|---|---|---|
| b | T | ||||
| a | F | ||||
| b | |||||
| a | |||||
| d |
i = 1, j = 1,b == b⇨ T
| b | a | b | a | d | |
|---|---|---|---|---|---|
| b | T | ||||
| a | F | T | |||
| b | |||||
| a | |||||
| d |
i = 2, j = 0. this is not a base case
| b | a | b | a | d | |
|---|---|---|---|---|---|
| b | T | ||||
| a | F | T | |||
| b | F | ||||
| a | |||||
| d |
i = 2, j = 1, falls in base case 2.
| b | a | b | a | d | |
|---|---|---|---|---|---|
| b | T | ||||
| a | F | T | |||
| b | F | T | |||
| a | |||||
| d |
i = 2, j = 2⇨ base case 1
| b | a | b | a | d | |
|---|---|---|---|---|---|
| b | T | ||||
| a | F | T | |||
| b | F | T | T | ||
| a | |||||
| d |
i = 3, j = 0⇨ not a base case similar todp[1][0], hencedp[3][0] = Fi = 3, j = 1⇨ Characters 'b' (i) and 'a' (j) are different, sodp[3][1]is set toFalse.i = 3, j = 2⇨ Base case. Characters 'b' (i) and 'b' (j) are the same andi - j(1). So,dp[3][2] = True.
| b | a | b | a | d | |
|---|---|---|---|---|---|
| b | T | ||||
| a | F | T | |||
| b | F | T | T | ||
| a | T | ||||
| d |
i = 3, j = 3⇨ Base case 1. So,dp[3][3] = True.
| b | a | b | a | d | |
|---|---|---|---|---|---|
| b | T | ||||
| a | F | T | |||
| b | F | T | T | ||
| a | T | T | |||
| d |
i = 4, j = 0⇨ Not a base case. Similar to previous cases,dp[4][0] = False.i = 4, j = 1⇨ Not a base case. Characters 'd' (i) and 'a' (j) are different, sodp[4][1]is set toFalse.i = 4, j = 2⇨ Not a base case. Characters 'd' (i) and 'b' (j) are different, sodp[4][2]is set toFalse.i = 4, j = 3⇨ Base case. Characters 'a' (i) and 'a' (j) are the same andi - j(1). So,dp[4][3] = True.
| b | a | b | a | d | |
|---|---|---|---|---|---|
| b | T | ||||
| a | F | T | |||
| b | F | T | T | ||
| a | T | T | |||
| d | T |
i = 4, j = 4⇨ Base case. Characters 'a' (i) and 'a' (j) are the same andi - j(1). So,dp[4][4] = True.
| b | a | b | a | d | |
|---|---|---|---|---|---|
| b | T | ||||
| a | F | T | |||
| b | F | T | T | ||
| a | T | T | |||
| d | T | T |
Finding the Longest Palindrome
Now that the DP table is filled, we need to find the longest palindrome based on the True values.
- We can see that
dp[3][2] = True(substring "bab" is a palindrome). - We also see that
dp[1][1] = True(substring "a" is a palindrome, but it's shorter than "bab"). - Since
dp[3][2]represents a longer palindrome (length 3), we update:maxLen = 3start = 2(index of 'b' in "bab")end = 4(index of 'b' in "bab" - remember indexing starts from 0)
Method 3 - Generate the Palindromes by expanding from middle 🏆
When we usually check the palindromes, we check it from outward to invward, i.e 2 pointers left and right move towards the centre. What if we start from center and look outward.
For eg. take the string babad. When i = 0, we have b. We cannot expand on left, so b is only palindrome. Lets have i = 1, we get a. Now, we can go both leftwards and rightwards, we get bab as the palindrome now.
So, when we go from point in array, say i, we expand on left and right; So, overall time complexity will be O(n^2).
The idea is to generate all even length and odd length palindromes and keep track of the longest palindrome seen so far.
- Step to generate odd length palindrome: Fix a centre and expand in both directions for longer palindromes.
- Step to generate even length palindrome: Fix two centre ( low and high ) and expand in both directions for longer palindromes.
Code
Java
class Solution {
public String longestPalindrome(String s) {
if (s == null || s.length() < 1) return "";
String ans = ""; // Store the result
int maxLen = 0; // Track maximum palindrome length found so far
for (int i = 0; i < s.length(); i++) {
// Odd-length palindrome
String oddPal = expandAroundCentre(s, i, i);
if (oddPal.length() > maxLen) {
ans = oddPal;
maxLen = oddPal.length();
}
// Even-length palindrome
String evenPal = expandAroundCentre(s, i, i + 1);
if (evenPal.length() > maxLen) {
ans = evenPal;
maxLen = evenPal.length();
}
}
return ans;
}
private String expandAroundCentre(String s, int left, int right) {
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
left--;
right++;
}
// Return the palindrome substring
return s.substring(left + 1, right);
}
}
Python
class Solution:
def longestPalindrome(self, s: str) -> str:
def expand_around_centre(left: int, right: int) -> str:
while left >= 0 and right < len(s) and s[left] == s[right]:
left -= 1
right += 1
# Return the palindrome substring
return s[left + 1:right]
ans: str = ""
max_len: int = 0
for i in range(len(s)):
# Odd-length palindrome
odd_pal: str = expand_around_centre(i, i)
if len(odd_pal) > max_len:
ans = odd_pal
max_len = len(odd_pal)
# Even-length palindrome
even_pal: str = expand_around_centre(i, i + 1)
if len(even_pal) > max_len:
ans = even_pal
max_len = len(even_pal)
return ans
Slightly cleaner code
We can make the code cleaner, by changing expandFromMiddle to return the length, and we create the substring only when needed:
Code
Java
public String longestPalindrome(String s) {
if (s == null || s.length() < 1) {
return "";
}
int start = 0;
int end = 0;
for (int i = 0; i < s.length(); i++) {
int len1 = expandFromMiddle(s, i, i);
int len2 = expandFromMiddle(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 static int expandFromMiddle(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 complexity:
O(n^2) - 🧺 Space complexity:
O(1)
Method 4 - Manacher's Algorithm
Manacher's algorithm is much more complicated to figure out, even though it will bring benefit of time complexity of O(n). Here it is: [Manachar’s Algorithm - Longest palindromic substring in linear time](manachar-s-algorithm-longest-palindromic-substring-in-linear-time).