Problem overview
LongestPalindrome receives a string s and returns its longest palindromic substring. A palindrome reads the same forward and backward. A substring is a run of consecutive characters. When several palindromes share the greatest length, the one that starts first wins. Uppercase and lowercase letters are different characters, so "Aa" is not a palindrome.
For "babad" the answer is "bab": "aba" is just as long, but "bab" starts first. For "cbbd" it is "bb", an even-length palindrome.
Approach: grow palindromes from their centers
Checking every substring takes O(n^3): about n^2 / 2 substrings, each up to n characters long to check. Look at palindromes from the middle instead. 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 of n characters has 2n - 1 centers, n characters and n - 1 gaps.
From a center, expand: put two positions left and right on it, and move them outward one step at a time while both are inside the string and their characters are equal. When they stop, the palindrome between them has right - left - 1 characters, and it is the longest one around that center.
For "babad" the helper gives these lengths for the odd center at i and the even center just after i:
0 odd=1 even=0
1 odd=3 even=0
2 odd=3 even=0
3 odd=1 even=0
4 odd=1 even=0
The first length of 3, at i = 1, is "bab". The same length at i = 2 is "aba", but it is not strictly longer, so "bab" stays.
Step by step
- Create
bestStartandbestLen, both 0. The start and the length are all you need to remember. The substring is cut out once, at the end. - Loop
iover every index. Eachigives two centers: the characters[i]and the gap after it. - Call
Expand(s, i, i)intoodd. Both ends start ons[i], which finds the longest odd-length palindrome with that middle. You can write this call beforeExpandexists and write the helper last. - Call
Expand(s, i, i + 1)intoeven. The ends start on two neighbours. If they differ, oriis the last index, the length is 0. - Take
Math.Max(odd, even)aslength, the longest palindrome around positioni. - Compare with
length > bestLen, strictly. The centers go from left to right, so a palindrome as long as the best one starts further right and must not replace it. - Set
bestStarttoi - (length - 1) / 2. An odd palindrome has(length - 1) / 2characters before its middles[i]. An even one haslength / 2 - 1characters befores[i], its left middle. Integer division rounds both down to(length - 1) / 2. - Set
bestLentolength. - Return
s.Substring(bestStart, bestLen)after the loop. - Write
Expand. It takes the string and two ends, and returns anint. - Loop while
left >= 0,right < s.Lengthands[left] == s[right]. The range checks come first, and&&stops at the first false one, so the characters are read only when both ends are inside the string. - 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. When the starting characters differ, that is 0.
The solution in C#
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, bestLen);
}
int Expand(string s, int left, int right) {
while (left >= 0 && right < s.Length && s[left] == s[right]) {
left--;
right++;
}
return right - left - 1;
}
}
The C# details worth knowing:
s.Substring(start, length)takes a start index and a number of characters, not an end index.leftandrightareints, passed by value. Moving them insideExpanddoes not change anything inLongestPalindrome, which is why the helper returns a length.s[left]is achar, and==on twochars compares their values.'A'and'a'are different values.- A method without an access modifier, like
Expand, isprivatein a class. The same class can call it by its name alone.
Complexity
Time: O(n^2). There are 2n - 1 centers and each expansion takes at most about n / 2 steps. A string like "aaaa" reaches that worst case.
Space: O(1) besides the returned string: a few integers.
An even palindrome is why the helper is called twice per index. In "cbbd", Expand(s, 1, 2) starts on the two bs, which match, then moves to c and d, which do not. The loop stops with left = 0 and right = 3, so the length is 3 - 0 - 1 = 2. For "abc", Expand(s, 2, 3) stops at once, because right is already past the end, and returns 3 - 2 - 1 = 0.
Edge cases and common mistakes
- One character.
"a"returns"a". The odd expansion gives 1. - No repeated letters.
"abc"returns"a", the first character, because every palindrome has length 1 and only a strictly longer one replaces the best. - Case matters.
"Aa"returns"A". - Whole string.
"racecar"returns"racecar", and"aaaa"returns"aaaa". - Forgetting even centers. Using only
Expand(s, i, i)misses"bb"in"cbbd"and returns"c". - Using
>=instead of>. Ties would go to the later palindrome:"babad"would return"aba". - Off-by-one in the start. The start comes from the center and the length, not from
leftandrightafter the loop, which have moved one step too far.
Testing
"babad"givesbab."cbbd"givesbb."abacdfgdcaba"givesaba."a"givesa."Aa"givesA."abc"givesa."aaaa"givesaaaa."racecar"givesracecar.
