Problem overview
The method countPalindromes receives a string, input, and returns how many of its contiguous substrings are palindromes. Substrings with different start or end indices are counted separately, even when they hold the same characters.
For "abbcbc" the answer is 9. The six single characters are palindromes. So are "bb" (indices 1 to 2), "bcb" (indices 2 to 4) and "cbc" (indices 3 to 5). The single "b" at index 1 and the one at index 2 are two substrings, since their positions differ, and both are counted.
Approach: try every substring and test it
A substring is fixed by its start index i and its end index j, with i <= j. A string of length n has n * (n + 1) / 2 of them. The direct plan is to loop over all pairs (i, j), test each substring, and count the ones that pass.
The test is the two-pointer palindrome check. One pointer starts at i and the other at j. While the left one is before the right one, the characters they point to must be equal, and then both pointers move inward. A mismatch means the substring isn't a palindrome.
The test works on the indices i and j of the original string, so it never builds the substring itself. That avoids copying characters for every pair.
This is the simplest correct approach. Its cost grows quickly with the length of the string, and the next lesson on this problem counts the same palindromes by expanding outward from each center, which is faster.
Counting by start index shows the loops at work on "abbcbc":
i = 0: "a" -> 1
i = 1: "b", "bb" -> 2
i = 2: "b", "bcb" -> 2
i = 3: "c", "cbc" -> 2
i = 4: "b" -> 1
i = 5: "c" -> 1
The sum is 9. For each start, the inner loop also tries the longer substrings, such as "bbc" or "bcbc", but they fail the test and add nothing.
Step by step
- Declare
resultas 0. It is the running count of palindromes. - Loop
iover the indices ofinput. It is the start of the substring. - Loop
jfromito the last index. It is the end of the substring. Starting atimakes the one-character substringinput[i]the first one tried. - Call
this.isPalindrome(input, i, j), and when it returnstrue, add 1 toresult. The lesson assumes the helper exists and writes it afterwards. - Return
resultwhen both loops are done. - Declare
isPalindromewith the string and the two indicesleftandright, returning aboolean. - Loop while
left < right. The pointers walk toward the middle, and the loop ends when they meet or cross. - Return
falseon a mismatch: ifinput[left] !== input[right], the substring can't be a palindrome. - Move the pointers:
left++andright--. - Return
trueafter the loop. Every pair matched.
The solution in TypeScript
class Solution {
isPalindrome(input: string, left: number, right: number): boolean {
while (left < right) {
if (input[left] !== input[right]) {
return false;
}
left++;
right--;
}
return true;
}
countPalindromes(input: string): number {
let result = 0;
for (let i = 0; i < input.length; i++) {
for (let j = i; j < input.length; j++) {
if (this.isPalindrome(input, i, j)) {
result++;
}
}
}
return result;
}
}
The TypeScript details worth knowing:
- Parameters can be changed.
isPalindromemovesleftandrightdirectly. They are local copies, since numbers are passed by value, so the loop variablesiandjin the caller are untouched. - Calling a method with
this. Inside the class,isPalindromeis a method of the object, so the call isthis.isPalindrome(...). The method appears beforecountPalindromesin the class, but the order doesn't matter for methods. - Strings can be indexed.
input[left]is a one-character string, and!==compares two such strings by content. - Types are only for the compiler. The annotations are removed when TypeScript is compiled to JavaScript, so they don't add any work at run time.
Complexity
Time: O(n³). There are about n²/2 pairs (i, j). For each pair, isPalindrome compares at most half of the substring, which is up to n / 2 steps. The worst case is a string of identical characters, where every test runs to the end. For most strings, tests stop early at the first mismatch, but the bound has to cover the worst case.
Space: O(1). The code uses a few numbers and no extra arrays or substrings.
Edge cases and common mistakes
- Empty string. The loops never run and the result is 0.
- All different characters.
"abc"gives 3: only the single characters are palindromes. - All the same character.
"aaa"gives 6: three single characters, two of length 2 and one of length 3. Every substring counts. - Starting
jati + 1. That skips the single-character substrings, so the count would be too low byn. - Counting distinct palindromes. The problem counts positions, not distinct strings. In
"abbcbc"the two single"b"substrings both count. - Building substrings. Calling
slicefor every pair, then reversing and comparing, gives the right answer but copies characters for every pair. Working with indices avoids that.
Testing
"abbcbc"gives9."abc"gives3."aaa"gives6.""gives0.