647. Palindromic Substrings - Explanation

Problem Link

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: 3

Explanation: "a", "b", "c".

Example 2:

Input: s = "aaa"

Output: 6

Explanation: "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 <= 1000
  • s consists 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.


Company Tags

Please upgrade to NeetCode Pro to view company tags.



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

  1. Initialize a counter res = 0
  2. Use two loops:
    • First loop picks the start index i
    • Second loop picks the end index j
  3. For each substring s[i : j+1]:
    • Set two pointers l = i, r = j
    • While l < r and characters match:
      • Move l right and r left
    • If pointers cross (l >= r), it is a palindrome
      • Increment res
  4. After checking all substrings, return res
class Solution:
    def countSubstrings(self, s: str) -> int:
        res = 0

        for i in range(len(s)):
            for j in range(i, len(s)):
                l, r = i, j
                while l < r and s[l] == s[r]:
                    l += 1
                    r -= 1
                res += (l >= r)

        return res

Time & Space Complexity

  • Time complexity: O(n3)O(n ^ 3)
  • Space complexity: O(1)O(1)

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

  1. Create a 2D DP table dp[i][j]
    • dp[i][j] = true if substring s[i..j] is a palindrome
  2. Initialize a counter res = 0
  3. Traverse the string from bottom to top for i
    • This ensures dp[i+1][j-1] is already computed
  4. For each (i, j) where j >= i:
    • If s[i] == s[j] AND
      (j - i <= 2 OR dp[i+1][j-1] == true)
      • Mark dp[i][j] = true
      • Increment res
  5. Return res
class Solution:
    def countSubstrings(self, s: str) -> int:
        n, res = len(s), 0
        dp = [[False] * n for _ in range(n)]

        for i in range(n - 1, -1, -1):
            for j in range(i, n):
                if s[i] == s[j] and (j - i <= 2 or dp[i + 1][j - 1]):
                    dp[i][j] = True
                    res += 1

        return res

Time & Space Complexity

  • Time complexity: O(n2)O(n ^ 2)
  • Space complexity: O(n2)O(n ^ 2)

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

  1. Initialize res = 0
  2. For each index i in the string:
    • Odd-length palindromes
      • Set l = i, r = i
      • While l >= 0, r < n, and s[l] == s[r]:
        • Increment res
        • Expand: l--, r++
    • Even-length palindromes
      • Set l = i, r = i + 1
      • While l >= 0, r < n, and s[l] == s[r]:
        • Increment res
        • Expand: l--, r++
  3. 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 res

Time & Space Complexity

  • Time complexity: O(n2)O(n ^ 2)
  • Space complexity: O(1)O(1)

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:

  1. A single character → odd-length palindromes
  2. 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

  1. Initialize res = 0
  2. For each index i in the string:
    • Add palindromes from odd center: countPali(s, i, i)
    • Add palindromes from even center: countPali(s, i, i + 1)
  3. In countPali(s, l, r):
    • While l >= 0, r < n, and s[l] == s[r]:
      • Increment res
      • Expand outward: l--, r++
  4. 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 res

Time & Space Complexity

  • Time complexity: O(n2)O(n ^ 2)
  • Space complexity: O(1)O(1)

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:

  1. 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.
  2. 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 i inside [l, r], we know its mirror position mirror = l + (r - i).
    • The palindrome radius at i can be at least the smaller of:
      • how much space remains inside the window (r - i)
      • the palindrome radius at mirror (p[mirror])
    • Then we try to expand further only if possible.

This avoids repeated expansions and ensures total work is linear.

Algorithm

  1. Build transformed string t by inserting # between characters and at ends.
  2. Create array p where p[i] = radius of palindrome centered at i in t.
  3. Track the current palindrome boundaries [l, r].
  4. For each index i:
    • If i is inside [l, r], initialize p[i] using its mirror.
    • Expand outward while characters match.
    • If palindrome at i extends beyond r, update [l, r].
  5. Convert radii in p into the number of palindromic substrings in the original string:
    • Each center contributes (p[i] + 1) // 2
  6. 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 res

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(n)O(n)

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.