Problem
Given a string, count how many of its substrings are palindromes โ sequences that read the same forwards and backwards. Every individual character counts as a palindrome on its own.
- Input:
s = "aaa" - Output:
6 - Explanation: The palindromic substrings are
"a","a","a","aa","aa", and"aaa".
Counter-example: "abc" produces 3 โ only the three single characters, since no multi-character substring reads the same in both directions.
Intuition
Every palindrome has a center: either a single character (odd length) or the gap between two adjacent characters (even length). If you stand at a center and expand outward one character at a time, every step where the left and right characters match reveals another palindrome. The key insight is that all valid expansions from a center form a chain โ once a mismatch occurs, no further expansion from that center can be palindromic.
Approach 1 โ Expand Around Center
For each of the 2n โ 1 possible centers, expand outward while the flanking characters match. Each successful expansion contributes one palindrome to the count.
- Iterate over every index as a potential center.
- For odd-length palindromes, expand starting with both pointers at the same index.
- For even-length palindromes, expand starting with the left pointer at the index and the right pointer one ahead.
- In each expansion, advance inward-out while bounds hold and characters match, incrementing the count each step.
- Return the total count.
1def countSubstrings(s: str) -> int:
2 count = 0
3
4 def expand_from_center(left: int, right: int) -> int:
5 palindromes = 0
6 while left >= 0 and right < len(s) and s[left] == s[right]:
7 palindromes += 1
8 left -= 1
9 right += 1
10 return palindromes
11
12 for center in range(len(s)):
13 count += expand_from_center(center, center) # odd-length: single char center
14 count += expand_from_center(center, center + 1) # even-length: gap between chars
15 return countTime: O(nยฒ) โ at most n expansions per center, across 2n โ 1 centers.
Space: O(1) โ only two pointer variables per expansion; no auxiliary storage.
Approach 2 โ Dynamic Programming
Build a 2-D boolean table where dp[start][end] is true when s[start..end] is a palindrome. Fill it by increasing substring length so that shorter results are always available when computing longer ones.
- Create an
n ร nboolean table, initially all false. - Iterate length from 1 to n.
- For each starting index, compute the ending index.
- Mark
dp[start][end]true when the outer characters match and the inner substring (already computed) is also a palindrome. Length 1 and 2 need no inner lookup. - Increment the count whenever a cell is marked true.
1def countSubstrings(s: str) -> int:
2 n = len(s)
3 count = 0
4 dp = [[False] * n for _ in range(n)]
5
6 for length in range(1, n + 1):
7 for start in range(n - length + 1):
8 end = start + length - 1
9 # length <= 2: no inner substring to check, outer match is sufficient
10 if s[start] == s[end] and (length <= 2 or dp[start + 1][end - 1]):
11 dp[start][end] = True
12 count += 1
13 return countTime: O(nยฒ) โ filling every cell in the n ร n table once.
Space: O(nยฒ) โ the DP table stores a boolean for every substring.
Complexity Summary
| Approach | Time | Space | When to use |
|---|---|---|---|
| Expand Around Center | O(nยฒ) | O(1) | Default choice โ minimal overhead, easy to implement |
| Dynamic Programming | O(nยฒ) | O(nยฒ) | When you need the full palindrome table for follow-up queries (e.g., reusing it in a partitioning problem) |
Common Mistakes
- Forgetting even-length palindromes โ expanding only from a single center character finds odd-length palindromes like
"aba"but misses"aa". A second expansion starting at(center, center + 1)is required. - Checking characters before checking bounds โ
s[left] == s[right]beforeleft >= 0 && right < ncauses an index-out-of-bounds when the expansion reaches the string edge. Bounds must come first in the condition. - Iterating DP by start index instead of by length โ if you loop over
startin the outer loop,dp[start + 1][end - 1]references a cell for a longer interval that hasn't been computed yet. The outer loop must be overlength. - Omitting the length โค 2 guard in DP โ for a length-2 substring
s[i..i+1],dp[i+1][i]is out-of-order and undefined. The conditionlength <= 2short-circuits this lookup, treating a matching pair of adjacent characters as a base case. - Returning a substring instead of a count โ the problem asks how many palindromic substrings exist, not which one is longest. A common slip is returning
max_lengthor the actual substring, especially after recently solving Longest Palindromic Substring.
Related Problems
longest-palindromic-substringโ identical technique, but tracks the longest palindrome found rather than counting all of thempalindrome-partitioningโ uses palindrome checking as a subroutine to split a string into all-palindrome parts via backtrackinglongest-palindromic-subsequenceโ palindrome DP on non-contiguous characters rather than substringsvalid-palindromeโ foundational palindrome check that underpins all palindrome problemsvalid-palindrome-iiโ extends the two-pointer palindrome check to allow one character removal