Problem overview
The method countPalindromes receives a string input and returns how many of its contiguous substrings are palindromes. Substrings at different positions count separately, even when they contain the same characters.
For "abbcbc" the answer is 9. The palindromic substrings are the six single characters a, b, b, c, b, c, plus bb, bcb and cbc. The two b characters and the two c characters each count separately, because they sit at different positions.
Approach: try every substring
A substring is identified by its first index i and its last index j, with i <= j. A string of length n has n * (n + 1) / 2 of them. The direct approach tests each one with a palindrome check and counts the successes.
The palindrome check works on the original string, without cutting out the substring. It uses two pointers: l at the first index and r at the last one. While l < r, compare the characters. If they differ, the substring is not a palindrome. If they match, move both pointers inwards. If the pointers meet or cross without a mismatch, it is a palindrome. Every one-character substring passes at once, because l and r are equal and the loop never starts.
For "abbcbc" the loops visit 21 substrings, since 6 * 7 / 2 = 21, and 9 of them pass the check: the six single characters and bb, bcb and cbc. Every other pair of indices fails at its first or second comparison, such as ab (a against b) or abbc (a against c).
This approach is easy to get right, but it does a lot of repeated work: checking "abbcb" and then "bbcb" each starts from scratch. The next lesson on this problem counts around centers and does much less.
Step by step
- Create
resultas avarstarting at 0. It counts the palindromes. - Loop over the first index
iwith0 until input.length. - Loop over the last index
jwithi until input.length. Starting atiincludes the one-character substring. - Test the substring. If
isPalindrome(input, i, j)is true, add 1 toresult. - Return
resultafter both loops. - Write
isPalindromeabovecountPalindromes. It takes the string and the two indicesleftandright, and returns aBoolean. - Copy the indices into variables. Parameters can't be reassigned in Scala, so
var l = leftandvar r = rightgive the loop something to change. - Loop while
l < r, comparing characters from both ends. - On a mismatch, when
input.charAt(l) != input.charAt(r), returnfalse. - Move inwards with
l += 1andr -= 1. - Return
truewhen the loop ends without a mismatch.
The solution in Scala
class Solution {
def isPalindrome(input: String, left: Int, right: Int): Boolean = {
var l = left
var r = right
while (l < r) {
if (input.charAt(l) != input.charAt(r)) {
return false
}
l += 1
r -= 1
}
return true
}
def countPalindromes(input: String): Int = {
var result = 0
for (i <- 0 until input.length) {
for (j <- i until input.length) {
if (isPalindrome(input, i, j)) {
result += 1
}
}
}
return result
}
}
The Scala details worth a closer look:
- Parameters are values.
leftandrightcan't be changed, so the method copies them into thevarslandr. input.charAt(l)is the JavaStringmethod, which Scala strings provide.input(l)reads the same character in the usual Scala style. Both work.!=compares the twoCharvalues by value.- Ranges.
0 until input.lengthstops before the length, whilei until input.lengthstarts at the currenti. Eachforover a range runs its body once per value. - Early return from a method.
return falseinside thewhileleavesisPalindromeimmediately. The finalreturn trueis the normal ending.
A shorter version and its cost
The same idea can be written by cutting out each substring and comparing it with its reverse:
val input = "abbcbc"
var result = 0
for (i <- 0 until input.length; j <- i until input.length) {
val sub = input.substring(i, j + 1)
if (sub == sub.reverse) result += 1
}
println(result) // 9
Here one for holds both ranges, separated by a semicolon. The code is shorter, but substring and reverse each build a new string for every pair, so it uses extra memory and does not improve the O(n³) time. The version with two pointers avoids those copies.
Complexity
Time: O(n³). There are about n² / 2 pairs (i, j), and each check can compare up to half of the substring's characters, which is up to n / 2 comparisons. For a string of one repeated letter, such as "aaaa", every check goes all the way through.
Space: O(1). The method keeps a few integers and builds no substrings.
Edge cases and common mistakes
- Empty string. Both loops run zero times, so the result is 0.
- One character.
"a"gives 1. - All characters equal.
"aaaa"gives 10, which is4 * 5 / 2: every substring is a palindrome. - No repeats.
"abc"gives 3, one for each single character. - Even-length palindromes.
"abba"gives 6: four single characters,bbandabba. The two-pointer check handles even and odd lengths the same way. - Starting
jati + 1. That drops all the one-character palindromes, and the example would give 3 instead of 9. - Counting distinct palindromes. The problem counts positions, not distinct texts. In
"abbcbc"the letterbadds 3, not 1. - Running
jup toinput.lengthinclusive. The last valid index isinput.length - 1, sountil input.lengthis right. Usingtowould read one position past the end of the string and throw an exception.
Testing
"abbcbc"gives9."aaaa"gives10."abc"gives3."abba"gives6.""gives0.
