# 647. Palindromic Substrings

LeetCode Problem Link (opens new window)

Given a string, your task is to count how many palindromic substrings are in it.

Substrings with different start or end positions are counted as different substrings even if they consist of the same characters.

Example 1:

  • Input: "abc"
  • Output: 3
  • Explanation: Three palindromic substrings are: "a", "b", "c"

Example 2:

  • Input: "aaa"
  • Output: 6
  • Explanation: Six palindromic substrings are: "a", "a", "a", "aa", "aa", "aaa"

Constraints: The length of the input string will not exceed 1000.

# Approach

# Brute Force

A brute force solution would use two nested for loops to iterate through all possible substrings and then a third loop to check if the current substring is a palindrome. This results in a time complexity of O(n^3).

# Dynamic Programming

The five steps of dynamic programming:

  1. Define the dp array (dp table) and the meaning of the indices

For most sequence-related problems, you naturally define the dp array based on what you are asked to find. However, for this problem, if we define dp[i] as the number of palindromic substrings ending at index i, it becomes hard to find a relation between dp[i], dp[i-1], and dp[i+1].

Thus, we need to consider the properties of palindromic substrings. When determining if a string S is a palindrome, if you know that the substring s[1] to s[3] is a palindrome, you only need to check if s[0] is equal to s[4] for S to be a palindrome.

This naturally leads to a recursive relation, judging whether a substring (from index [i,j]) is a palindrome depends on whether the substring s[i + 1, j - 1] is a palindrome.

Given this, we need a two-dimensional dp array.

Define dp[i][j]: A boolean indicator showing whether the substring in the range [i,j] (both inclusive) is a palindromic substring. If true, it’s a palindrome; if false, it is not.

  1. Determine the recurrence relation

Analyzing different scenarios when s[i] is equal to s[j] leads us to three cases:

  • Case 1: If indices i and j are the same, it is a single character, e.g., "a", which is a palindrome.
  • Case 2: If indices i and j differ by 1, e.g., "aa", it is also a palindrome.
  • Case 3: If i and j differ by more than 1, e.g., "cabac", since s[i] equals s[j], check if the substring b[i+1] to b[j-1] (i.e., "aba") is a palindrome, which means dp[i + 1][j - 1] should be true.

Based on this analysis, the recurrence relation is:

if (s[i] == s[j]) {
    if (j - i <= 1) { // Case 1 and Case 2
        result++;
        dp[i][j] = true;
    } else if (dp[i + 1][j - 1]) { // Case 3
        result++;
        dp[i][j] = true;
    }
}
1
2
3
4
5
6
7
8
9

The result tracks the count of palindromic substrings.

Notice that since dp[i][j] is initialized to false, if s[i] does not equal s[j], it does not affect our initialization.

  1. Initialize the dp array

We cannot initialize dp[i][j] to true. It starts as false since initially, no substring is considered a palindrome.

  1. Determine the traversal order

The traversal order is crucial.

The recurrence relation depends on dp[i + 1][j - 1]. Hence, the matrix should be filled such that dp[i + 1][j - 1] values are calculated before dp[i][j]. This can be accomplished by traversing from bottom to top, left to right.

Some implementations iterate columns first, then rows, which satisfies the same dependency.

Here is the code:

for (int i = s.size() - 1; i >= 0; i--) {  // Ensure the correct traversal order
    for (int j = i; j < s.size(); j++) {
        if (s[i] == s[j]) {
            if (j - i <= 1) { // Case 1 and Case 2
                result++;
                dp[i][j] = true;
            } else if (dp[i + 1][j - 1]) { // Case 3
                result++;
                dp[i][j] = true;
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
  1. Example to illustrate the dp array

For input "aaa", the dp[i][j] becomes:

In the diagram, with 6 truths, we accurately account for six palindromic substrings.

Note that due to the definition of dp[i][j], j must be greater than or equal to i, ensuring only the upper half of the matrix is filled.

Complete analysis and solution below:

class Solution {
public:
    int countSubstrings(string s) {
        vector<vector<bool>> dp(s.size(), vector<bool>(s.size(), false));
        int result = 0;
        for (int i = s.size() - 1; i >= 0; i--) {  // Correct traversal order necessary
            for (int j = i; j < s.size(); j++) {
                if (s[i] == s[j]) {
                    if (j - i <= 1) { // Case 1 and Case 2
                        result++;
                        dp[i][j] = true;
                    } else if (dp[i + 1][j - 1]) { // Case 3
                        result++;
                        dp[i][j] = true;
                    }
                }
            }
        }
        return result;
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

The code is simplified for clarity:

class Solution {
public:
    int countSubstrings(string s) {
        vector<vector<bool>> dp(s.size(), vector<bool>(s.size(), false));
        int result = 0;
        for (int i = s.size() - 1; i >= 0; i--) {
            for (int j = i; j < s.size(); j++) {
                if (s[i] == s[j] && (j - i <= 1 || dp[i + 1][j - 1])) {
                    result++;
                    dp[i][j] = true;
                }
            }
        }
        return result;
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  • Time Complexity: O(n^2)
  • Space Complexity: O(n^2)

# Two-Pointer Approach

Since dynamic programming consumes more space, let's explore the two-pointer approach.

The approach basically focuses on expanding around the center and checking symmetry. Two center types must be considered:

  1. A single character center.
  2. A pair of characters as a center.

Some may argue there could be centers with three characters, but they can be derived by adding characters on sides of a single character. Similarly, a center with four characters can be derived via a double-character center.

Thus, handling these two cases separately is clearer:

class Solution {
public:
    int countSubstrings(string s) {
        int result = 0;
        for (int i = 0; i < s.size(); i++) {
            result += extend(s, i, i, s.size()); // Centered at single character
            result += extend(s, i, i + 1, s.size()); // Centered between two characters
        }
        return result;
    }
    int extend(const string& s, int i, int j, int n) {
        int res = 0;
        while (i >= 0 && j < n && s[i] == s[j]) {
            i--;
            j++;
            res++;
        }
        return res;
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  • Time Complexity: O(n^2)
  • Space Complexity: O(1)

# Other Language Versions

# Java:

Dynamic Programming:

class Solution {
    public int countSubstrings(String s) {
        char[] chars = s.toCharArray();
        int len = chars.length;
        boolean[][] dp = new boolean[len][len];
        int result = 0;
        for (int i = len - 1; i >= 0; i--) {
            for (int j = i; j < len; j++) {
                if (chars[i] == chars[j]) {
                    if (j - i <= 1) { // Case 1 and Case 2
                        result++;
                        dp[i][j] = true;
                    } else if (dp[i + 1][j - 1]) { // Case 3
                        result++;
                        dp[i][j] = true;
                    }
                }
            }
        }
        return result;
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

Dynamic Programming: Simplified Version

class Solution {
    public int countSubstrings(String s) {
        boolean[][] dp = new boolean[s.length()][s.length()];
        
        int res = 0;
        for (int i = s.length() - 1; i >= 0; i--) {
            for (int j = i; j < s.length(); j++) {
                if (s.charAt(i) == s.charAt(j) && (j - i <= 1 || dp[i + 1][j - 1])) {
                    res++;
                    dp[i][j] = true;
                }
            }
        }
        return res;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Center Expansion Method:

class Solution {
    public int countSubstrings(String s) {
        int len, ans = 0;
        if (s == null || (len = s.length()) < 1) return 0;
        // There are 2 * len - 1 centers total
        for (int i = 0; i < 2 * len - 1; i++) {
            // Traverse each center and expand while checking if it is a palindrome
            int left = i / 2, right = left + i % 2;
            while (left >= 0 && right < len && s.charAt(left) == s.charAt(right)) {
                // If current is a palindrome, increase count
                ans++;
                left--;
                right++;
            }
        }
        return ans;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

LeetCode 5. Longest Palindromic Substring (a variation of LeetCode 647 can solve LeetCode 5 with some adjustments)

class Solution {
    public String longestPalindrome(String s) {
        int finalStart = 0; 
        int finalEnd = 0;
        int finalLen = 0;

        char[] chars = s.toCharArray();
        int len = chars.length;

        boolean[][] dp = new boolean[len][len];
        for (int i = len - 1; i >= 0; i--) {
            for (int j = i; j < len; j++) {
                if (chars[i] == chars[j] && (j - i <= 1 || dp[i + 1][j - 1]))
                        dp[i][j] = true;
                if (dp[i][j] && j - i + 1 > finalLen) {
                    finalLen = j - i + 1;
                    finalStart = i;
                    finalEnd = j;
                }
            }
        }
        return s.substring(finalStart, finalEnd + 1);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# Python:

Dynamic Programming:

class Solution:
    def countSubstrings(self, s: str) -> int:
        dp = [[False] * len(s) for _ in range(len(s))]
        result = 0
        for i in range(len(s)-1, -1, -1): # Ensure correct traversal order
            for j in range(i, len(s)):
                if s[i] == s[j]:
                    if j - i <= 1: # Case 1 and Case 2
                        result += 1
                        dp[i][j] = True
                    elif dp[i+1][j-1]: # Case 3
                        result += 1
                        dp[i][j] = True
        return result
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Dynamic Programming: Simplified Version

class Solution:
    def countSubstrings(self, s: str) -> int:
        dp = [[False] * len(s) for _ in range(len(s))]
        result = 0
        for i in range(len(s)-1, -1, -1): # Ensure correct traversal order
            for j in range(i, len(s)):
                if s[i] == s[j] and (j - i <= 1 or dp[i+1][j-1]): 
                    result += 1
                    dp[i][j] = True
        return result
1
2
3
4
5
6
7
8
9
10

Two-Pointer Method:

class Solution:
    def countSubstrings(self, s: str) -> int:
        result = 0
        for i in range(len(s)):
            result += self.extend(s, i, i, len(s)) # Centered on single character
            result += self.extend(s, i, i+1, len(s)) # Centered between two characters
        return result
    
    def extend(self, s, i, j, n):
        res = 0
        while i >= 0 and j < n and s[i] == s[j]:
            i -= 1
            j += 1
            res += 1
        return res
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# Go:

Dynamic Programming:

func countSubstrings(s string) int {
    res:=0
    dp:=make([][]bool,len(s))
    for i:=0;i<len(s);i++{
        dp[i]=make([]bool,len(s))
    }

    for i:=len(s)-1;i>=0;i--{
        for j:=i;j<len(s);j++{
            if s[i]==s[j]{
                if j-i<=1{
                    res++
                    dp[i][j]=true
                }else if dp[i+1][j-1]{
                    res++
                    dp[i][j]=true
                }
            }
        }
    }
    return res
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

Dynamic Programming: Simplified Version

func countSubstrings(s string) int {
	res := 0
	dp := make([][]bool, len(s))
	for i := 0; i < len(s); i++ {
		dp[i] = make([]bool, len(s))
	}

	for i := len(s) - 1; i >= 0; i-- {
		for j := i; j < len(s); j++ {
			if s[i] == s[j] && (j-i <= 1 || dp[i+1][j-1]) {
				res++
				dp[i][j] = true
			}
		}
	}
	return res
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

Two-Pointer Method:

func countSubstrings(s string) int {
	extend := func(i, j int) int {
		res := 0
		for i >= 0 && j < len(s) && s[i] == s[j] {
			i --
			j ++
			res ++
		}
		return res
	}
	res := 0
	for i := 0; i < len(s); i++ {
		res += extend(i, i)  // Centered at a single character
		res += extend(i, i+1) // Centered between two characters
	}
	return res
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# JavaScript:

Dynamic Programming

const countSubstrings = (s) => {
    const strLen = s.length;
    let numOfPalindromicStr = 0;
    let dp = Array.from(Array(strLen), () => Array(strLen).fill(false));

    for(let j = 0; j < strLen; j++) {
        for(let i = 0; i <= j; i++) {
            if(s[i] === s[j]) {
                if((j - i) < 2) {
                    dp[i][j] = true;
                } else {
                    dp[i][j] = dp[i+1][j-1];
                }
                numOfPalindromicStr += dp[i][j] ? 1 : 0;
            }
        }
    }

    return numOfPalindromicStr;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

Two-Pointer Method:

const countSubstrings = (s) => {
    const strLen = s.length;
    let numOfPalindromicStr = 0;

    for(let i = 0; i < 2 * strLen - 1; i++) {
        let left = Math.floor(i/2);
        let right = left + i % 2;

        while(left >= 0 && right < strLen && s[left] === s[right]){
            numOfPalindromicStr++;
            left--;
            right++;
        }
    }

    return numOfPalindromicStr;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# TypeScript:

Dynamic Programming:

function countSubstrings(s: string): number {
    /**
        dp[i][j]: [i,j] range's substring is a palindrome (both inclusive)
     */
    const length: number = s.length;
    const dp: boolean[][] = new Array(length).fill(0)
        .map(_ => new Array(length).fill(false));
    let resCount: number = 0;
    // Bottom-up, left-to-right traversal
    for (let i = length - 1; i >= 0; i--) {
        for (let j = i; j < length; j++) {
            if (
                s[i] === s[j] &&
                (j - i <= 1 || dp[i + 1][j - 1] === true)
            ) {
                dp[i][j] = true;
                resCount++;
            }
        }
    }
    return resCount;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

Two-Pointer Method:

function countSubstrings(s: string): number {
    const length: number = s.length;
    let resCount: number = 0;
    for (let i = 0; i < length; i++) {
        resCount += expandRange(s, i, i);
        resCount += expandRange(s, i, i + 1);
    }
    return resCount;
};
function expandRange(s: string, left: number, right: number): number {
    let palindromeNum: number = 0;
    while (
        left >= 0 && right < s.length &&
        s[left] === s[right]
    ) {
        palindromeNum++;
        left--;
        right++;
    }
    return palindromeNum;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

Rust:

Dynamic Programming:

impl Solution {
    pub fn count_substrings(s: String) -> i32 {
        let mut dp = vec![vec![false; s.len()]; s.len()];
        let mut res = 0;

        for i in (0..s.len()).rev() {
            for j in i..s.len() {
                if s[i..=i] == s[j..=j] && (j - i <= 1 || dp[i + 1][j - 1]) {
                    dp[i][j] = true;
                    res += 1;
                }
            }
        }
        res
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Two-Pointer Method:

impl Solution {
    pub fn count_substrings(s: String) -> i32 {
        let mut res = 0;
        for i in 0..s.len() {
            res += Self::extend(&s, i, i, s.len());
            res += Self::extend(&s, i, i + 1, s.len());
        }
        res
    }

    fn extend(s: &str, mut i: usize, mut j: usize, len: usize) -> i32 {
        let mut res = 0;
        while i < len && j < len && s[i..=i] == s[j..=j] {
            res += 1;
            i = i.wrapping_sub(1);
            j += 1;
        }
        res
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Last updated:: 9/4/2025, 3:19:38 PM
Copyright © 2025 keetcoder