647. Palindromic Substrings
Problem Description
You are given a string s
. Your task is to count how many palindromic substrings exist within this string.
A palindrome is a string that reads the same forwards and backwards. For example, "aba", "racecar", and "a" are palindromes.
A substring is any contiguous sequence of characters taken from the original string. For instance, if s = "abc"
, then the substrings are: "a", "b", "c", "ab", "bc", and "abc".
The solution uses an expand-around-center approach. It iterates through 2n - 1
possible centers (where n
is the length of the string). Why 2n - 1
? Because palindromes can have either odd length (centered at a single character) or even length (centered between two characters).
For each center position k
:
- When
k
is even, it represents a center at character indexk // 2
(odd-length palindromes) - When
k
is odd, it represents a center between characters at indicesk // 2
and(k + 1) // 2
(even-length palindromes)
The algorithm expands outward from each center using two pointers i
and j
. As long as the characters at positions i
and j
match and stay within bounds, it counts this as a valid palindrome and continues expanding by moving i
left and j
right.
The ~i
condition checks if i >= 0
(since ~i
is false when i
is -1), and j < n
ensures we don't go past the string's end. Each valid expansion represents a palindromic substring, so the counter is incremented accordingly.
Intuition
The key insight is that every palindrome has a center. If we can identify all possible centers in a string and expand outward from each center, we can find all palindromic substrings.
Consider how palindromes are structured:
- Odd-length palindromes like "aba" have a single character as their center
- Even-length palindromes like "abba" have their center between two characters
This means for a string of length n
, we have:
n
possible centers for odd-length palindromes (at each character)n - 1
possible centers for even-length palindromes (between each pair of adjacent characters)- Total:
2n - 1
possible centers
Instead of checking every possible substring to see if it's a palindrome (which would be inefficient), we can be smarter. Starting from each potential center, we expand outward symmetrically. As long as the characters on both sides match, we have found a valid palindrome.
The beauty of this approach is that once characters don't match, we can stop expanding from that center immediately. This is because if the outer characters don't match, adding more characters even further out won't create a palindrome either.
By using a single loop that handles both odd and even-length palindromes through clever index calculation (i = k // 2
and j = (k + 1) // 2
), we avoid code duplication. When k
is even, i
and j
start at the same position (odd-length center). When k
is odd, they start at adjacent positions (even-length center).
This expand-around-center technique efficiently finds all palindromic substrings in O(n²)
time, which is optimal for this problem since there can be up to O(n²)
palindromic substrings in the worst case.
Learn more about Two Pointers and Dynamic Programming patterns.
Solution Approach
The implementation uses the expand-around-center technique with a single loop to handle both odd and even-length palindromes.
Step-by-step walkthrough:
-
Initialize variables: Set
ans = 0
to count palindromes and get the string lengthn
. -
Iterate through all possible centers: Loop through
k
from0
to2n - 2
(total of2n - 1
iterations). -
Calculate starting positions for expansion:
i = k // 2
- left pointer starting positionj = (k + 1) // 2
- right pointer starting position
When
k
is even (0, 2, 4...):- Both
i
andj
point to the same index (e.g., whenk = 0
:i = 0, j = 0
) - This handles odd-length palindromes centered at a single character
When
k
is odd (1, 3, 5...):i
andj
point to adjacent indices (e.g., whenk = 1
:i = 0, j = 1
)- This handles even-length palindromes centered between two characters
-
Expand around the center:
- The while loop condition
~i and j < n and s[i] == s[j]
checks:~i
: Ensuresi >= 0
(bitwise NOT of -1 gives 0, which is falsy)j < n
: Ensures we don't exceed the string boundarys[i] == s[j]
: Characters match, forming a palindrome
- The while loop condition
-
Count and continue expanding:
- When all conditions are met, increment
ans
by 1 - Expand outward:
i -= 1
andj += 1
- Continue until characters don't match or boundaries are reached
- When all conditions are met, increment
-
Return the total count of palindromic substrings.
Example trace for s = "aaa"
:
k = 0
: Start at (0,0), expand to find "a"k = 1
: Start at (0,1), expand to find "aa"k = 2
: Start at (1,1), expand to find "a", "aaa"k = 3
: Start at (1,2), expand to find "aa"k = 4
: Start at (2,2), expand to find "a"- Total: 6 palindromic substrings
The algorithm efficiently explores all possible palindromes with O(n²)
time complexity and O(1)
space complexity.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's trace through the algorithm with s = "aba"
:
String visualization:
Index: 0 1 2 Char: a b a
We'll iterate through 2n - 1 = 5
possible centers (k = 0 to 4):
k = 0 (odd-length center at index 0):
i = 0 // 2 = 0
,j = 1 // 2 = 0
- Start at position (0, 0):
s[0] = 'a'
,s[0] = 'a'
✓ Match - Count palindrome "a",
ans = 1
- Expand:
i = -1
,j = 1
- Stop (i < 0)
k = 1 (even-length center between indices 0 and 1):
i = 1 // 2 = 0
,j = 2 // 2 = 1
- Start at position (0, 1):
s[0] = 'a'
,s[1] = 'b'
✗ No match - Stop immediately
k = 2 (odd-length center at index 1):
i = 2 // 2 = 1
,j = 3 // 2 = 1
- Start at position (1, 1):
s[1] = 'b'
,s[1] = 'b'
✓ Match - Count palindrome "b",
ans = 2
- Expand:
i = 0
,j = 2
- Check position (0, 2):
s[0] = 'a'
,s[2] = 'a'
✓ Match - Count palindrome "aba",
ans = 3
- Expand:
i = -1
,j = 3
- Stop (i < 0)
k = 3 (even-length center between indices 1 and 2):
i = 3 // 2 = 1
,j = 4 // 2 = 2
- Start at position (1, 2):
s[1] = 'b'
,s[2] = 'a'
✗ No match - Stop immediately
k = 4 (odd-length center at index 2):
i = 4 // 2 = 2
,j = 5 // 2 = 2
- Start at position (2, 2):
s[2] = 'a'
,s[2] = 'a'
✓ Match - Count palindrome "a",
ans = 4
- Expand:
i = 1
,j = 3
- Stop (j >= n)
Final answer: 4 palindromic substrings ("a", "b", "aba", "a")
Solution Implementation
1class Solution:
2 def countSubstrings(self, s: str) -> int:
3 """
4 Count the number of palindromic substrings in a string.
5
6 Uses the expand-around-center technique to check all possible palindrome centers.
7 Each character and gap between characters can be a center for palindromes.
8
9 Args:
10 s: Input string
11
12 Returns:
13 Total count of palindromic substrings
14 """
15 count = 0
16 n = len(s)
17
18 # Iterate through all possible palindrome centers
19 # For a string of length n, there are (2n - 1) possible centers:
20 # n centers at each character (for odd-length palindromes)
21 # n-1 centers between characters (for even-length palindromes)
22 for center in range(2 * n - 1):
23 # Calculate left and right pointers based on center position
24 # For even center values (0, 2, 4...): left = right = center // 2
25 # For odd center values (1, 3, 5...): left = center // 2, right = center // 2 + 1
26 left = center // 2
27 right = (center + 1) // 2
28
29 # Expand around center while characters match
30 while left >= 0 and right < n and s[left] == s[right]:
31 # Found a palindrome
32 count += 1
33 # Expand outward
34 left -= 1
35 right += 1
36
37 return count
38
1class Solution {
2 /**
3 * Counts the number of palindromic substrings in the given string.
4 * Uses the expand-around-center approach to find all palindromes.
5 *
6 * @param s the input string
7 * @return the total count of palindromic substrings
8 */
9 public int countSubstrings(String s) {
10 int palindromeCount = 0;
11 int stringLength = s.length();
12
13 // Iterate through all possible palindrome centers (2n-1 total)
14 // Centers include both characters (n) and gaps between characters (n-1)
15 for (int centerIndex = 0; centerIndex < stringLength * 2 - 1; centerIndex++) {
16 // Calculate left and right pointers based on center index
17 // For even centerIndex: both pointers start at centerIndex/2 (odd-length palindrome)
18 // For odd centerIndex: pointers start at adjacent positions (even-length palindrome)
19 int leftPointer = centerIndex / 2;
20 int rightPointer = (centerIndex + 1) / 2;
21
22 // Expand around the center while characters match
23 while (leftPointer >= 0 && rightPointer < stringLength &&
24 s.charAt(leftPointer) == s.charAt(rightPointer)) {
25 // Found a palindrome, increment counter
26 palindromeCount++;
27
28 // Expand outward from center
29 leftPointer--;
30 rightPointer++;
31 }
32 }
33
34 return palindromeCount;
35 }
36}
37
1class Solution {
2public:
3 int countSubstrings(string s) {
4 int palindromeCount = 0;
5 int stringLength = s.size();
6
7 // Iterate through all possible palindrome centers (2n - 1 total)
8 // This includes n single characters and n-1 positions between characters
9 for (int centerIndex = 0; centerIndex < stringLength * 2 - 1; ++centerIndex) {
10 // Calculate left and right pointers based on center index
11 // For even centerIndex (0, 2, 4...): represents single character center
12 // - left = centerIndex / 2, right = (centerIndex + 1) / 2 (same position)
13 // For odd centerIndex (1, 3, 5...): represents position between two characters
14 // - left = centerIndex / 2, right = (centerIndex + 1) / 2 (adjacent positions)
15 int left = centerIndex / 2;
16 int right = (centerIndex + 1) / 2;
17
18 // Expand around the center while characters match
19 // ~left is equivalent to left >= 0 (bitwise NOT of -1 is 0)
20 while (left >= 0 && right < stringLength && s[left] == s[right]) {
21 // Found a palindrome substring from s[left] to s[right]
22 ++palindromeCount;
23
24 // Expand the window
25 --left;
26 ++right;
27 }
28 }
29
30 return palindromeCount;
31 }
32};
33
1/**
2 * Counts the number of palindromic substrings in a given string
3 * @param s - The input string to search for palindromic substrings
4 * @returns The total count of palindromic substrings
5 */
6function countSubstrings(s: string): number {
7 let count: number = 0;
8 const length: number = s.length;
9
10 // Iterate through all possible palindrome centers (including between characters)
11 // For a string of length n, there are n centers for odd-length palindromes
12 // and n-1 centers for even-length palindromes, totaling 2n-1 centers
13 for (let centerIndex = 0; centerIndex < length * 2 - 1; centerIndex++) {
14 // Calculate left pointer position
15 // For odd-length palindromes: centerIndex is even, left starts at centerIndex/2
16 // For even-length palindromes: centerIndex is odd, left starts at (centerIndex-1)/2
17 let left: number = centerIndex >> 1;
18
19 // Calculate right pointer position
20 // For odd-length palindromes: right starts at same position as left
21 // For even-length palindromes: right starts at left + 1
22 let right: number = (centerIndex + 1) >> 1;
23
24 // Expand around the center while characters match and indices are valid
25 // ~left is equivalent to left >= 0 (bitwise NOT of -1 equals 0)
26 while (~left && right < length && s[left] === s[right]) {
27 // Found a palindrome, increment counter
28 count++;
29
30 // Expand the window by moving pointers outward
31 left--;
32 right++;
33 }
34 }
35
36 return count;
37}
38
Time and Space Complexity
Time Complexity: O(n²)
The algorithm iterates through 2n - 1
possible centers for palindromes (n single-character centers and n-1 between-character centers). For each center position k
, it expands outward using two pointers i
and j
. In the worst case, when the entire string is the same character (e.g., "aaaa"), each expansion can extend to the boundaries of the string, taking O(n)
time. Therefore, the overall time complexity is O(n) * O(n) = O(n²)
.
Space Complexity: O(1)
The algorithm uses only a constant amount of extra space. The variables ans
, n
, k
, i
, and j
are the only additional storage used, regardless of the input size. No additional data structures like arrays or recursion stacks are employed, making the space complexity constant.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Confusing the center indexing logic
Many developers struggle to understand why we need 2n - 1
centers and how the index calculation works. A common mistake is trying to handle odd and even-length palindromes in separate loops, which leads to more complex and error-prone code:
Incorrect approach:
# Separate loops for odd and even palindromes
count = 0
# Check odd-length palindromes
for i in range(len(s)):
left, right = i, i
while left >= 0 and right < len(s) and s[left] == s[right]:
count += 1
left -= 1
right += 1
# Check even-length palindromes
for i in range(len(s) - 1):
left, right = i, i + 1
while left >= 0 and right < len(s) and s[left] == s[right]:
count += 1
left -= 1
right += 1
Solution: Use the unified approach with k // 2
and (k + 1) // 2
to handle both cases in a single loop. This reduces code duplication and potential for errors.
2. Off-by-one errors in the loop range
A frequent mistake is using range(2 * n)
instead of range(2 * n - 1)
, which would cause an index out of bounds error when accessing the string.
Incorrect:
for center in range(2 * n): # Wrong! Goes up to 2n-1, but we only have 2n-1 centers
left = center // 2
right = (center + 1) // 2 # When center = 2n-1, right = n, causing index error
Solution: Always use range(2 * n - 1)
to iterate through exactly 2n - 1
centers.
3. Forgetting to count single characters as palindromes
Some implementations mistakenly skip counting single characters or start expanding without counting the initial center position:
Incorrect:
while left >= 0 and right < n and s[left] == s[right]: left -= 1 right += 1 count += 1 # Counting AFTER expansion misses the center itself
Solution: Count the palindrome BEFORE expanding (increment count first, then move pointers).
4. Using complex boundary checks
The ~i
notation might be confusing. Some developers try to "simplify" it but end up with incorrect logic:
Incorrect attempts:
# Wrong: This would stop at i = 0
while i > 0 and j < n and s[i] == s[j]:
# Overly complex:
while i != -1 and j != len(s) and s[i] == s[j]:
Solution: Use the clear and standard check left >= 0 and right < n
. The ~i
trick works but can reduce code readability.
5. Not handling edge cases
Failing to consider edge cases like empty strings or single-character strings:
Potential issue:
if not s: # Unnecessary check that adds complexity
return 0
if len(s) == 1: # Also unnecessary
return 1
Solution: The expand-around-center algorithm naturally handles all edge cases:
- Empty string:
range(2 * 0 - 1)
produces no iterations, returns 0 - Single character: Correctly counts 1 palindrome
- All same characters: Correctly counts all possible palindromic substrings
Problem: Given a list of tasks and a list of requirements, compute a sequence of tasks that can be performed, such that we complete every task once while satisfying all the requirements.
Which of the following method should we use to solve this problem?
Recommended Readings
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!