Problem overview
The method longestPalindrome receives a string s and returns its longest palindromic substring. A palindrome reads the same forward and backward, like "racecar" or "abba". A substring is a run of consecutive characters, so "bcd" is a substring of "abcde" but "ace" is not. When several palindromes share the greatest length, the one that starts furthest to the left is returned.
For "babad" the result is "bab". The substring "aba" has the same length, but "bab" starts first. For "cbbd" the result is "bb", an even-length palindrome. For "abacdfgdcaba" it is "aba", which appears at both ends, and the first one counts. The string contains English letters and digits, and case matters, so "Aa" is not a palindrome.
The statement asks for O(n²) time and O(1) extra space besides the returned string.
Approach: grow palindromes from their centers
Checking every substring separately costs O(n³): there are about n²/2 substrings and each check reads up to n characters. The faster view starts from the middle. Every palindrome has a center: its middle character when the length is odd, or the gap between its two middle characters when the length is even. A string with n characters has 2n - 1 centers, which are n characters and n - 1 gaps.
From a center, expand: put left and right on the two middle positions and move them outward while both stay inside the string and the characters match. When the loop stops, the palindrome between the two positions is the longest one around that center.
The loop visits each index i and tries two centers: the character s[i] (start with left = right = i) and the gap after it (start with left = i, right = i + 1). For "babad" the expansions give these lengths:
i=0 odd=1 even=0
i=1 odd=3 even=0
i=2 odd=3 even=0
i=3 odd=1 even=0
i=4 odd=1 even=0
At i = 1 the odd expansion finds "bab" (length 3). At i = 2 it finds "aba", also length 3, but the best is replaced only by a strictly longer palindrome, so "bab" stays. Centers are visited from left to right, so this rule keeps the first of the longest ones.
Other methods exist. A table dp[i][j] that says whether s[i..j] is a palindrome has the same time but needs O(n²) space, and Manacher's algorithm reaches O(n) time. This lesson uses center expansion because it is short and meets the space limit.
Step by step
- Create
bestStartandbestLen, both 0. Remembering the start and the length is enough, and the substring is cut out once at the end. - Loop
iover every index ofs. Each index supplies two centers: a character and the gap after it. - Call
expand(s, i, i)and store the result inodd. The helper is written last. Assuming it exists keeps this loop short. - Call
expand(s, i, i + 1)and store the result ineven. When the two characters differ, oriis the last index, the length is 0. - Take the larger of the two with
Math.max, aslength. - Compare with
length > bestLen. It is strict, so a palindrome that is just as long but starts later never replaces the earlier one. - Compute the start as
i - (length - 1) / 2. An odd palindrome has(length - 1) / 2characters before its middle. An even one haslength / 2 - 1characters before its left middle, and with integer division both come to(length - 1) / 2. - Update
bestLentolength. - Return
s.substring(bestStart, bestStart + bestLen). - Write
expandas a method of the same class that returns anintand takes the string and the two ends. - Loop while the ends are valid and equal:
left >= 0,right < s.length()and the two characters match. - Move outward:
left--andright++. - Return
right - left - 1. The loop stops one step past the palindrome on each side, so the palindrome runs fromleft + 1toright - 1.
The solution in Java
class Solution {
public String longestPalindrome(String s) {
int bestStart = 0;
int bestLen = 0;
for (int i = 0; i < s.length(); i++) {
int odd = expand(s, i, i);
int even = expand(s, i, i + 1);
int length = Math.max(odd, even);
if (length > bestLen) {
bestStart = i - (length - 1) / 2;
bestLen = length;
}
}
return s.substring(bestStart, bestStart + bestLen);
}
int expand(String s, int left, int right) {
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
left--;
right++;
}
return right - left - 1;
}
}
The Java details worth a closer look:
- Short-circuit
&&. The two bounds checks come befores.charAt. When one fails, Java skips the rest, socharAtis never called with an index outside the string, which would throw aStringIndexOutOfBoundsException. charAtand==.s.charAt(i)returns achar, a primitive, so==compares the characters themselves. That differs from strings, which needequals. Characters are compared by code, which is why'A'and'a'differ.- Arguments are passed by value. Changing
leftandrightinsideexpanddoesn't affect the caller's variables. The caller only receives the returned length. - Integer division.
(length - 1) / 2drops the remainder. Sincelengthis at least 1 whenever theifruns, the numerator is never negative, so this rounds down. substring(start, end)leaves out the end index, sobestStart + bestLenas the end gives exactlybestLencharacters.- Method without a modifier.
expandhas default (package-private) access, which is fine for a helper called from the same class.
Complexity
There are 2n - 1 centers, and each expansion takes at most about n / 2 steps, so the time is O(n²). The method keeps only a few integers, so the extra space is O(1), not counting the returned string.
Edge cases and common mistakes
- Forgetting even-length palindromes. Calling only
expand(s, i, i)misses"bb"in"cbbd"and would return"b". - Using
>=in the comparison. A later palindrome of the same length would replace the earlier one. For"babad"the result would be"aba"instead of"bab". - Checking the characters before the bounds. Reversing the order of the conditions in the
whileloop throws an exception at the ends of the string. - Wrong length formula. The loop stops one step beyond the palindrome on both sides, which is why the length is
right - left - 1and notright - left. - No palindrome longer than one character. Any single character is a palindrome, so
"abcd"returns"a", the first one. - Case.
"Aa"returns"A", because uppercase and lowercase letters differ.
Testing
These results come from running the solution:
s | Result |
|---|---|
"babad" | "bab" |
"cbbd" | "bb" |
"abacdfgdcaba" | "aba" |
"a" | "a" |
"ab" | "a" |
"aa" | "aa" |
"aaaa" | "aaaa" |
"Aa" | "A" |
"forgeeksskeegfor" | "geeksskeeg" |
