647. Palindromic Substrings - Explanation
Description
Given a string s, return the number of substrings within s that are palindromes.
A palindrome is a string that reads the same forward and backward.
Example 1:
Input: s = "abc"
Output: 3Explanation: "a", "b", "c".
Example 2:
Input: s = "aaa"
Output: 6Explanation: "a", "a", "a", "aa", "aa", "aaa". Note that different substrings are counted as different palindromes even if the string contents are the same.
Constraints:
1 <= s.length <= 1000sconsists of lowercase English letters.
Topics
Recommended Time & Space Complexity
You should aim for a solution as good or better than O(n^2) time and O(1) space, where n is the length of the given string.
Hint 1
A brute-force solution would be to check if every substring is a palindrome and return the total number of palindromic substrings. This would be an O(n^3) time solution. Can you think of a better way? Perhaps you should consider thinking in terms of the center of a palindrome.
Hint 2
Iterate over the string with index i and treat the current character as the center. For this character, try to extend outward to the left and right simultaneously, but only if both characters are equal. At each iteration, we increment the count of palindromes. How would you implement this? Can you consider both cases: even-length and odd-length palindromes?
Hint 3
Initialize a variable res to track the count of palindromes. At each index, create an odd-length palindrome starting at that index extending outward from both its left and right indices, i.e., i - 1 and i + 1. How can you find the even-length palindrome for this index?
Hint 4
For an even-length palindrome, consider expanding from indices i and i + 1. This two-pointer approach, extending from the center of the palindrome, will help find all palindromic substrings in the given string and return its count.
Prerequisites
Before attempting this problem, you should be comfortable with:
- Two Pointers - Used to check if a substring is a palindrome by comparing characters from both ends
- Dynamic Programming (2D) - Needed for the DP approach to store palindrome status of substrings
- String Manipulation - Understanding how to extract and compare substrings efficiently
1. Brute Force
Intuition
A substring is palindromic if it reads the same forwards and backwards.
The brute-force idea is simple:
- Generate all possible substrings
- For each substring, check if it is a palindrome
- Count how many substrings satisfy this condition
To check a palindrome, use two pointers:
- One starting from the left
- One starting from the right
- Move inward while characters match
If the pointers cross (or meet), the substring is a palindrome.
Algorithm
- Initialize a counter
res = 0 - Use two loops:
- First loop picks the start index
i - Second loop picks the end index
j
- First loop picks the start index
- For each substring
s[i : j+1]:- Set two pointers
l = i,r = j - While
l < rand characters match:- Move
lright andrleft
- Move
- If pointers cross (
l >= r), it is a palindrome- Increment
res
- Increment
- Set two pointers
- After checking all substrings, return
res
Time & Space Complexity
- Time complexity:
- Space complexity:
2. Dynamic Programming
Intuition
A substring s[i..j] is palindromic if:
- The end characters match:
s[i] == s[j] - The inside substring
s[i+1..j-1]is also a palindrome
(or the length is ≤ 2, which is always a palindrome if ends match)
So instead of re-checking characters every time, we reuse previous results:
- Store whether a substring is palindromic in a DP table
- Build solutions for longer substrings using shorter ones
Algorithm
- Create a 2D DP table
dp[i][j]dp[i][j] = trueif substrings[i..j]is a palindrome
- Initialize a counter
res = 0 - Traverse the string from bottom to top for
i- This ensures
dp[i+1][j-1]is already computed
- This ensures
- For each
(i, j)wherej >= i:- If
s[i] == s[j]AND(j - i <= 2 OR dp[i+1][j-1] == true)- Mark
dp[i][j] = true - Increment
res
- Mark
- If
- Return
res
Time & Space Complexity
- Time complexity:
- Space complexity:
3. Two Pointers
Intuition
Every palindrome has a center:
- For odd-length palindromes, the center is a single character
- For even-length palindromes, the center is between two characters
Instead of checking all substrings, we:
- Fix a center
- Expand outwards as long as characters match
- Each successful expansion forms one palindrome
This way, we count palindromes directly while expanding.
Algorithm
- Initialize
res = 0 - For each index
iin the string:- Odd-length palindromes
- Set
l = i,r = i - While
l >= 0,r < n, ands[l] == s[r]:- Increment
res - Expand:
l--,r++
- Increment
- Set
- Even-length palindromes
- Set
l = i,r = i + 1 - While
l >= 0,r < n, ands[l] == s[r]:- Increment
res - Expand:
l--,r++
- Increment
- Set
- Odd-length palindromes
- Return
res
class Solution:
def countSubstrings(self, s: str) -> int:
res = 0
for i in range(len(s)):
# odd length
l, r = i, i
while l >= 0 and r < len(s) and s[l] == s[r]:
res += 1
l -= 1
r += 1
# even length
l, r = i, i + 1
while l >= 0 and r < len(s) and s[l] == s[r]:
res += 1
l -= 1
r += 1
return resTime & Space Complexity
- Time complexity:
- Space complexity:
4. Two Pointers (Optimal)
Intuition
Every palindromic substring can be identified by expanding from its center.
There are only two possible centers for any palindrome:
- A single character → odd-length palindromes
- The gap between two characters → even-length palindromes
Instead of duplicating logic for both cases, we extract the expansion logic into a helper function (countPali).
This keeps the solution clean, reusable, and optimal.
For each index i, we:
- Count palindromes centered at
(i, i) - Count palindromes centered at
(i, i + 1)
Each successful expansion corresponds to one valid palindrome.
Algorithm
- Initialize
res = 0 - For each index
iin the string:- Add palindromes from odd center:
countPali(s, i, i) - Add palindromes from even center:
countPali(s, i, i + 1)
- Add palindromes from odd center:
- In
countPali(s, l, r):- While
l >= 0,r < n, ands[l] == s[r]:- Increment
res - Expand outward:
l--,r++
- Increment
- While
- Return total count
class Solution:
def countSubstrings(self, s: str) -> int:
res = 0
for i in range(len(s)):
res += self.countPali(s, i, i)
res += self.countPali(s, i, i + 1)
return res
def countPali(self, s, l, r):
res = 0
while l >= 0 and r < len(s) and s[l] == s[r]:
res += 1
l -= 1
r += 1
return resTime & Space Complexity
- Time complexity:
- Space complexity:
5. Manacher's Algorithm
Intuition
The “expand around center” idea is great, but it can redo the same comparisons many times, making it O(n^2).
Manacher’s Algorithm speeds this up to O(n) by using two tricks:
Normalize odd/even palindromes
- Insert a separator like
#between characters:"abba"→"#a#b#b#a#" - Now every palindrome in this new string has an odd-length center, so we only handle one case.
- Insert a separator like
Reuse information using a “current best palindrome window”
- Maintain a palindrome window
[l, r](the farthest-reaching palindrome found so far). - For a new position
iinside[l, r], we know its mirror positionmirror = l + (r - i). - The palindrome radius at
ican be at least the smaller of:- how much space remains inside the window (
r - i) - the palindrome radius at
mirror(p[mirror])
- how much space remains inside the window (
- Then we try to expand further only if possible.
- Maintain a palindrome window
This avoids repeated expansions and ensures total work is linear.
Algorithm
- Build transformed string
tby inserting#between characters and at ends. - Create array
pwherep[i]= radius of palindrome centered atiint. - Track the current palindrome boundaries
[l, r]. - For each index
i:- If
iis inside[l, r], initializep[i]using its mirror. - Expand outward while characters match.
- If palindrome at
iextends beyondr, update[l, r].
- If
- Convert radii in
pinto the number of palindromic substrings in the original string:- Each center contributes
(p[i] + 1) // 2
- Each center contributes
- Sum contributions and return
res.
class Solution:
def countSubstrings(self, s: str) -> int:
def manacher(s):
t = '#' + '#'.join(s) + '#'
n = len(t)
p = [0] * n
l, r = 0, 0
for i in range(n):
p[i] = min(r - i, p[l + (r - i)]) if i < r else 0
while (i + p[i] + 1 < n and i - p[i] - 1 >= 0
and t[i + p[i] + 1] == t[i - p[i] - 1]):
p[i] += 1
if i + p[i] > r:
l, r = i - p[i], i + p[i]
return p
p = manacher(s)
res = 0
for i in p:
res += (i + 1) // 2
return resTime & Space Complexity
- Time complexity:
- Space complexity:
Common Pitfalls
Forgetting Even-Length Palindromes
When using the expand-around-center approach, many developers only expand from single characters (odd-length centers). Even-length palindromes like "aa" or "abba" require expanding from between two characters, so both (i, i) and (i, i+1) centers must be checked.
Off-By-One Errors in DP Table Indexing
In the DP approach, the recurrence dp[i][j] = dp[i+1][j-1] requires careful iteration order. Processing i from high to low ensures dp[i+1][j-1] is computed before dp[i][j]. Iterating in the wrong direction leads to reading uninitialized values.
Counting Substrings Instead of Counting Once Per Palindrome
Some implementations accidentally count the same palindrome multiple times or miss counting single characters as palindromes. Every single character is itself a palindrome, so the minimum count for a string of length n is n.
Sign in to join the discussion