Problem overview
CountPalindromes takes a string input and returns the number of palindromic contiguous substrings. Substrings with different start or end indexes are counted separately, even when the letters are the same. For "abbcbc" the answer is 9: the six single letters, bb, bcb and cbc.
Approach
The first approach to this problem tests every substring, copying and reversing each one, which costs O(n³). This lesson uses a different view. Every palindrome has a center, and it can be grown outward from there:
- An odd-length palindrome has one middle character. Its center is a single index
i. - An even-length palindrome has two middle characters, which must be equal. Its center sits between indexes
iandi + 1.
Starting from a center, the helper compares the characters on the two sides. If they match, that is one more palindrome, and the window grows by one on each side. If they differ, or the window runs off either end of the string, no wider window around this center can be a palindrome, so the helper stops. The number of matches it found is the number of palindromes with that center.
A string of length n has n odd centers and n - 1 even centers. Every palindromic substring has exactly one center, so adding up the counts from all of them gives each palindrome once.
For "abbcbc" the odd centers give 1, 1, 1, 2, 2, 1. The two 2s come from the c at index 3, which also yields bcb, and the b at index 4, which also yields cbc. That sums to 8. The even centers give one match, bb, between indexes 1 and 2, for a total of 9.
One expansion in detail
Take the odd center at index 3 of "abbcbc". The helper starts with left = 3 and right = 3. Both point at c, so the count becomes 1 and the window widens to indexes 2 and 4. Both hold b, so the count becomes 2 and the window widens to 1 and 5. Now input[1] is b and input[5] is c, so the loop ends and the helper returns 2: the palindromes c and bcb. The work stopped at the first mismatch and never looked at a.
Step by step
- Declare
resultinCountPalindromes, starting at 0. - Loop over every index
ias a possible center. - Add the odd-length count by calling
CountPalindromesWithCenter(input, i, i). Both pointers start on the same character. - Add the even-length count with
CountPalindromesWithCenter(input, i, i + 1). If those two characters differ, the helper returns 0 immediately. - Return
resultafter the loop. - Write the helper. It takes the string and two indices
leftandright, and returns anint. - Start its own
resultat 0 and loop whileleftis at least 0,rightis less thaninput.Length, andinput[left]equalsinput[right]. - Inside the loop, count the palindrome, then widen the window:
leftgoes down by one andrightgoes up by one. - Return the count when the loop ends.
The solution in C#
class Solution {
public int CountPalindromesWithCenter(string input, int left, int right) {
int result = 0;
while (0 <= left && right < input.Length &&
input[left] == input[right]) {
result += 1;
left -= 1;
right += 1;
}
return result;
}
public int CountPalindromes(string input) {
int result = 0;
for (int i = 0; i < input.Length; i++) {
result += CountPalindromesWithCenter(input, i, i);
result += CountPalindromesWithCenter(input, i, i + 1);
}
return result;
}
}
C# details:
- Short-circuit
&&. C# evaluates the conditions left to right and stops at the first false one. The two index checks come beforeinput[left] == input[right], so the string is never indexed with -1 or withinput.Length. Reordering the checks would throwIndexOutOfRangeException. - Parameters are copies. The helper changes
leftandrightfreely. They are separate local variables, and the caller'siis unaffected. - Comparing
charvalues.input[left] == input[right]compares two characters directly, with no string created. - The call at the last index is safe. For the last
i, the even-center call starts withrightequal to the length, so it fails theright < input.Lengthtest first and returns 0.
Complexity
Time: O(n²). There are 2n - 1 centers, and expanding from one takes at most about n/2 steps. The worst case is a string of one repeated letter such as "aaaa", where every window keeps matching. A string with few matches finishes much faster, since expansion stops at the first mismatch.
Space: O(1). Only integers are stored. No substrings are created.
Edge cases and common mistakes
- Forgetting the even centers. Without the second call,
bbin the example is missed, and the result is 8. - Counting a palindrome twice. Each palindrome has one center, so odd and even centers never overlap. The count is exact.
- Checking the characters before the bounds. Putting
input[left] == input[right]first in the condition crashes at the string's edges. - The empty string. The loop doesn't run, so the result is 0.
- All the same letter.
"aaa"gives 6, since the three single letters, two"aa"and one"aaa"are counted. This is also the slowest input for the algorithm.
Testing
"abbcbc"gives9."aaa"gives6.""gives0.