OK, Let’s continue….
Problem Statement:
Given a string s, find the longest palindromic sub string in s. You may assume that the maximum length of s is 1000.
Example 1:
Input: "babad" Output: "bab" Note: "aba" is also a valid answer.
Example 2:
Input: "cbbd" Output: "bb"
Solution One: Brute Force
Simple but not efficient. Iterate all possible sub string, then test whether it is a palindromic string. If it is, then check whether the length is longer than the cached longest length. It is so slow, almost useless.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Solution { | |
public: | |
bool isPalindrome(string &s) | |
{ | |
int len = s.size(); | |
for(int i = 0; i< len/2; i++) | |
{ | |
if(s[i] != s[len – i – 1]) | |
return false; | |
} | |
return true; | |
} | |
string longestPalindrome(string s) { | |
int len = s.size(); | |
int iStart = 0, Max = 0; | |
string temp; | |
for(int i =0; i< len; i++) | |
{ | |
for (int j = i; j < len ; j++) | |
{ | |
temp = s.substr(i, j-i+1); | |
if(isPalindrome(temp) && j-i + 1 > Max) | |
{ | |
Max = j-i+1; | |
iStart = i; | |
} | |
} | |
} | |
return s.substr(iStart, Max); | |
} | |
}; |
Time Complexity: O(n^3)
Space Complexity: O(1)
Solution Two: Extension from Center
The general idea for this approach is to firstly select a center, and then extend from this center to check whether the sub string is palindromic. Since the longest palindromic string might have even or odd number of characters, so when we pick up the center, we should both pick each character (odd sub string) and the gap between each string (even sub string). As a result of this, the total number of iteration should be n + n – 1, which is 2*n – 1.
Each time when we make an extension, we check whether the new sub string is palindromic. If it is, we update the max length variable. If it is not, we move on. The code is pretty straightforward.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Solution { | |
public: | |
string longestPalindrome(string s) { | |
int start = 0, end = 0; | |
int len = 0; | |
for(int i = 0; i< s.size(); i++) | |
{ | |
int len1 = PalindromeLength(s, i, i); | |
int len2 = PalindromeLength(s, i, i+1); | |
len = max(len1, len2); | |
if(len > end – start) | |
{ | |
start = i – (len – 1)/2; | |
end = i + len/2; | |
} | |
} | |
return s.substr(start, end – start + 1); | |
} | |
int PalindromeLength(string &s, int Left, int Right) | |
{ | |
int L = Left, R = Right; | |
while(L>=0 && R< s.size() && s[L] == s[R]) | |
{ | |
L–; | |
R++; | |
} | |
return R – L – 1; | |
} | |
}; |
Time Complexity: O(n^2)
Space Complexity: O(1)
Solution Three: Optimized Brute Force
When we apply the brute force approach, we have to check every possible sub strings, but actually, we do not need to do every check if we cache some intermediate results. For example, if we already know that string [i+1, j-1] is a palindromic sub string, when we want to know whether [i, j] is a palindromic string, we only need to check whether s[i] == s[j].
This solution is not suitable when the sub string only contains one or two characters, since at that moment, i+1 is greater than j-1, which means we are not checking the correct sub string. We have to handle these two special cases when we run our algorithm.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Solution { | |
public: | |
string longestPalindrome(string s) { | |
if(s == "") | |
return s; | |
int len = s.size(); | |
int maxsLen = 0; | |
bool arr[len][len]; | |
string finalString; | |
for(int i = 1; i <= len; i++) | |
{ | |
for(int start = 0; start < len; start++) | |
{ | |
int end = start + i – 1; | |
if(end >= len) | |
break; | |
arr[start][end] = (i == 1 || i == 2 || arr[start + 1][end – 1])&&(s[start] == s[end]); | |
if(arr[start][end] && i > maxsLen) | |
{ | |
finalString = s.substr(start, i); | |
maxsLen = i; | |
} | |
} | |
} | |
return finalString; | |
} | |
}; |
Time Complexity: O(n^2)
Space Complexity: O(n^2)
Solution Four: Manacher’s algorithm
This algorithm seems especially designed for this problem. Basically, it is a little bit similar to central extension algorithm, but more tricky.
First thing let’s try to do some processing to our original array. When we start analyzing this problem, we have stated that the number of elements affect the final algorithm. Here, we insert some new elements to the original string and convert this string to be a string which only contains odd number of elements.
The rule is to insert ‘^’ and ‘$’ character to the both ends of the string, and insert ‘#’ character to all the gaps between original characters (see the picture below). When we finish the insertion, we will get a string which only contains odd number of elements. And when we do the central extension algorithm like we discussed above, we can aways guarantee we will exit the loop when the comparison hits both ends (‘^’ != ‘$’, always true).
When the insertion is over, we can have another array P to store the number of elements which extend from the center, this number is also the number of characters from the original array, which are involved during the extension:
For example, when we see that P[6] is actually 5. Now if we consider P[6] is the center, then we can see it extends to both sides 5 unites (‘#’ from both sides are considered as the same characters) before the two sides have different characters. And this number 5 is also all the characters involved in the original array, which is c-b-c-b-c. Or we can say, 5 is the length of this palindromic sub string.
If we know this, we can easily calculate the original array index. The starting index of the original array should be:
(i – P[i]) / 2
For example, if 6 – P[6], it is 1, then divided by 2, the number is 0, which corresponds to the first element of the original array, which is c. And the length of the sub array is P[6] = 5.
OK, the next is the key part of Manacher’s algorithm. It utilizes the symmetric feature of palindromic strings. Look at the picture below, let’s mark
We can see here, C is the center, and R is the maximum radius of the palindromic sub string, we define R as R = C + P[i]. C and R are in the current palindromic sub string and R is the most right boundary.
So, how can we calculate P[i]? If we do the central extension algorithm, we can extend to both sides. But here, we can utilize the symmetric feature of palindromic string, let’s define i_mirror is a mirror index of i related to the center C, so the i_mirror = 2 * C – i. In the above example, P[i_mirror] = 3, since the palindromic string is symmetric, so P[i] should be 3 as well. However, there are some special cases we need to handle before we assign the value of P[i_mirror] to P[i]:
- i + P[i] > R
In this case, when we try to assign value of P[i_mirror] to P[i], which is 7. We find that i + P[i] is greater than R, which is 22. Since R is the most right boundary of this palindromic sub string, if the P[i] + i > R, we cannot guarantee that all the right elements of the P[i] is indeed symmetric of its left part. The only thing we can guarantee is that elements i+1 to elements R should be symmetric to the left of P[i].
So, in this case, we have to assign P[i] = R – P[i] = 5. After that, we extend from R, and compare T[R+1] and T[R+1 symmetric to P[i]], if they are equal, we increase P[i]. Else we ignore.
- P[i_mirror] is out of the left boundary
Look at the picture below, should we assign P[i] = 1? No, that’s because the P[i_mirror] hits the left boundary for a second iteration. However, for our P[i], we did not encounter any boundry, we should extend like the central extension algorithm. If we found more symmetric elements, we need to update P[i].
- i equals R
First we set P[i] = 0; then we extend to both sides like before.
Whenever we have a larger boundary (P[i] + i > R) of P[i], we need to update C and R to the current palindromic string. Which means, we update:
C = P[i];
R = P[i] + i;
The reason we do this is because we need to guarantee that i is always inside boundary R.
For example, look at the picture below:
If we continue to calculate the P[i] now, the P[i] should finally become 10 + 3 = 13. (3 maximum symmetric elements, ‘#’ counts). We need to update R = 13, and C = 10. Then we continue our loop.
I hope I have explained all of the staff. Let’s look at the code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Solution { | |
public: | |
string preProcess(string s) | |
{ | |
int len = s.size(); | |
string proStr = "^"; | |
//string numberStr = "#"; | |
//string dollarStr = "$"; | |
for(int i = 0; i < len; i++) | |
{ | |
proStr.push_back('#'); | |
proStr.push_back(s[i]); | |
} | |
proStr.push_back('#'); | |
proStr.push_back('$'); | |
return proStr; | |
} | |
string longestPalindrome(string s) { | |
int len = s.size(); | |
if(len == 0) | |
return s; | |
string pS = preProcess(s); | |
int len_pS = pS.size(); | |
int C = 0, R = 0, i_mirror = 0; | |
int arr[len_pS]; | |
for(int i = 0; i < len_pS; i++) | |
{ | |
i_mirror = 2 * C – i; | |
if(i < R) | |
{ | |
arr[i] = min(R – i, arr[i_mirror]); | |
} | |
else | |
{ | |
arr[i] = 0; | |
} | |
while(pS[i+1 + arr[i]] == pS[i – 1 – arr[i]]) | |
{ | |
arr[i]++; | |
} | |
if(i + arr[i] > R) | |
{ | |
C = i; | |
R = i + arr[i]; | |
} | |
} | |
int maxLen = 0, start = 0; | |
for(int i = 0; i < len_pS; i++) | |
{ | |
if(arr[i]>maxLen){ | |
maxLen = arr[i]; | |
start = i; | |
} | |
} | |
start = (start – maxLen)/2; | |
return s.substr(start, maxLen); | |
} | |
}; |
Basically, we need to handle all the three cases separately. Whenever i + P[i] > R, we update C and R. As for the time complexity, when the algorithm runs, all the characters are at maximum touched twice, one for the linear forward searching, one for T[R + i] == T[(R + i)mirror], so the time complexity should be approximately 2*n, which is O(n).
Time Complexity: O(n)
Space Complexity: O(n)
That’s all for this article….