Palindromic Substrings
Palindromic Substrings
Given a string `s`, find and return the total number of sub strings in `s` that are palindromes.
A palindrome is a string that remains the same when read both forward and backward.
Output: 3
Output: 6
Constraints:
- 1 <= s.length <= 1000
- “s” consists of lowercase English letters.
Palindromic Substrings Solution
Approaches for solving Palindromic substrings problem
There are mainly 5 approach to solve this problem
- Brute Force Method
- Dynamic Programming Method
- Two Pointers Method
- Two Pointers (Optimal) Method
- Manacher’s algorithms
Approach 1 : Brute Force
- Check every possible substring in the string.
- For each substring, see if it reads the same forward and backward.
- This method is simple but slow because it checks all combinations.
Approach 3 : Two Pointers
- Think of each character (and pair of characters) as the center of a palindrome.
- Expand outward, checking if characters match, and count all palindromes found.
- This method is faster and uses less memory.
Approach 5 : Manacher’s Problem
- Preprocess the string with separators to unify palindrome checks, and maintain an array that tracks the radius of the longest palindrome at each position.
- Use symmetry to skip redundant checks and expand efficiently.
Approach 2 : Dynamic Programming
- Use a table to store if a sub string is a palindrome.
- Start with small sub strings (length 1 and 2), then use this information to check larger ones.
- This saves time by reusing results from smaller sub strings.
Approach 4 : Two Pointers (Optimal)
- Use two pointers to expand around potential centers of palindromes.
- Treat each character and pair of consecutive characters as centers (to handle odd and even-length palindromes).
- Expand outward from each center and count valid palindromic substrings.
1. Brute Force Method:
Check all possible substrings in the string and verify each one to see if it is a palindrome. This method is simple but slow because it explores all combinations.
- Time complexity: O(n^3)
- Space complexity: O(1)
Code
class Solution {
public:
int countSubstrings(string s) {
int res = 0;
for (int i = 0; i < s.size(); i++) {
for (int j = i; j < s.size(); j++) {
int l = i, r = j;
while (l < r && s[l] == s[r]) {
l++;
r--;
}
res += (l >= r);
}
}
return res;
}
};
public class Solution {
public int countSubstrings(String s) {
int res = 0;
for (int i = 0; i < s.length(); i++) {
for (int j = i; j < s.length(); j++) {
int l = i, r = j;
while (l < r && s.charAt(l) == s.charAt(r)) {
l++;
r--;
}
res += (l >= r) ? 1 : 0;
}
}
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
class Solution {
/**
* @param {string} s
* @return {number}
*/
countSubstrings(s) {
let res = 0;
for (let i = 0; i < s.length; i++) {
for (let j = i; j < s.length; j++) {
let l = i, r = j;
while (l < r && s[l] === s[r]) {
l++;
r--;
}
res += (l >= r) ? 1 : 0;
}
}
return res;
}
}
2. Dynamic Programming Method
Use a table to track if sub strings are palindromes, starting with smaller ones and building up to larger ones. This approach reuses previous results to avoid redundant checks.
- Time complexity: O(n^2)
- Space complexity: O(n^2)
Code
class Solution {
public:
int countSubstrings(string s) {
int res = 0, n = s.length();
vector> dp(n, vector(n, false));
for (int i = n - 1; i >= 0; i--) {
for (int j = i; j < n; j++) {
if (s[i] == s[j] &&
(j - i <= 2 || dp[i + 1][j - 1])) {
dp[i][j] = true;
res++;
}
}
}
return res;
}
};
public class Solution {
public int countSubstrings(String s) {
int res = 0, n = s.length();
boolean[][] dp = new boolean[n][n];
for (int i = n - 1; i >= 0; i--) {
for (int j = i; j < n; j++) {
if (s.charAt(i) == s.charAt(j) &&
(j - i <= 2 || dp[i + 1][j - 1])) {
dp[i][j] = true;
res++;
}
}
}
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
class Solution {
/**
* @param {string} s
* @return {number}
*/
countSubstrings(s) {
let res = 0;
const n = s.length;
const dp = Array.from({ length: n }, () => Array(n).fill(false));
for (let i = n - 1; i >= 0; i--) {
for (let j = i; j < n; j++) {
if (s[i] === s[j] &&
(j - i <= 2 || dp[i + 1][j - 1])) {
dp[i][j] = true;
res++;
}
}
}
return res;
}
}
3. Two Pointers Method
Expand around each character (or pair of characters) as the center of a potential palindrome. Count all valid palindromes found by comparing characters outward.
- Time complexity: O(n^2)
- Space complexity: O(1)
Code
class Solution {
public:
int countSubstrings(string s) {
int res = 0;
for (int i = 0; i < s.size(); i++) {
int l = i, r = i;
while (l >= 0 && r < s.size() &&
s[l] == s[r]) {
res++;
l--;
r++;
}
l = i;
r = i + 1;
while (l >= 0 && r < s.size() &&
s[l] == s[r]) {
res++;
l--;
r++;
}
}
return res;
}
};
public class Solution {
public int countSubstrings(String s) {
int res = 0;
for (int i = 0; i < s.length(); i++) {
int l = i, r = i;
while (l >= 0 && r < s.length() &&
s.charAt(l) == s.charAt(r)) {
res++;
l--;
r++;
}
l = i;
r = i + 1;
while (l >= 0 && r < s.length() &&
s.charAt(l) == s.charAt(r)) {
res++;
l--;
r++;
}
}
return res;
}
}
class Solution:
def countSubstrings(self, s: str) -> int:
res = 0
for i in range(len(s)):
l, r = i, i
while l >= 0 and r < len(s) and s[l] == s[r]:
res += 1
l -= 1
r += 1
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
class Solution {
/**
* @param {string} s
* @return {number}
*/
countSubstrings(s) {
let res = 0;
for (let i = 0; i < s.length; i++) {
let l = i;
let r = i;
while (l >= 0 && r < s.length &&
s.charAt(l) === s.charAt(r)) {
res++;
l--;
r++;
}
l = i;
r = i + 1;
while (l >= 0 && r < s.length &&
s.charAt(l) === s.charAt(r)) {
res++;
l--;
r++;
}
}
return res;
}
}
4. Two Pointers (Optimal) Method
Similar to the regular two-pointer method but optimized to handle odd and even-length palindromes efficiently. It avoids unnecessary checks and uses O(1) space.
- Time complexity: O(n^2)
- Space complexity: O(1)
Code
class Solution {
public:
int countSubstrings(string s) {
int res = 0;
for (int i = 0; i < s.size(); i++) {
res += countPali(s, i, i);
res += countPali(s, i, i + 1);
}
return res;
}
private:
int countPali(string s, int l, int r) {
int res = 0;
while (l >= 0 && r < s.size() && s[l] == s[r]) {
res++;
l--;
r++;
}
return res;
}
};
public class Solution {
public int countSubstrings(String s) {
int res = 0;
for (int i = 0; i < s.length(); i++) {
res += countPali(s, i, i);
res += countPali(s, i, i + 1);
}
return res;
}
private int countPali(String s, int l, int r) {
int res = 0;
while (l >= 0 && r < s.length() &&
s.charAt(l) == s.charAt(r)) {
res++;
l--;
r++;
}
return res;
}
}
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
class Solution {
/**
* @param {string} s
* @return {number}
*/
countSubstrings(s) {
let res = 0;
for (let i = 0; i < s.length; i++) {
res += this.countPali(s, i, i);
res += this.countPali(s, i, i + 1);
}
return res;
}
/**
* @param {string} s
* @param {number} l
* @param {number} r
* @return {number}
*/
countPali(s, l, r) {
let res = 0;
while (l >= 0 && r < s.length &&
s.charAt(l) === s.charAt(r)) {
res++;
l--;
r++;
}
return res;
}
}
5. Manacher’s Algorithm
This method transforms the string to handle odd and even-length palindromes uniformly by adding separators (e.g., #) between characters. It then uses a center-expansion technique with precomputed symmetry to find all palindromic substrings in O(n) time.
- Time complexity: O(n)
- Space complexity: O(n)
Code
class Solution {
public:
vector<int> manacher(string& s) {
if (!s.size()) return {};
string t = "#" + string(1, s[0]);
for (int i = 1; i < s.size(); ++i)
t += "#" + string(1, s[i]);
t += "#";
int n = t.size();
vector<int> p(n, 0);
int l = 0, r = 0;
for (int i = 0; i < n; i++) {
p[i] = (i < r) ? min(r - i, p[l + (r - i)]) : 0;
while (i + p[i] + 1 < n && i - p[i] - 1 >= 0 &&
t[i + p[i] + 1] == t[i - p[i] - 1])
p[i]++;
if (i + p[i] > r)
l = i - p[i], r = i + p[i];
}
return p;
}
int countSubstrings(string s) {
vector<int> p = manacher(s);
int res = 0;
for (int i : p) {
res += (i + 1) / 2;
}
return res;
}
};
public class Solution {
public int[] manacher(String s) {
StringBuilder t = new StringBuilder("#");
for (char c : s.toCharArray()) {
t.append(c).append("#");
}
int n = t.length();
int[] p = new int[n];
int l = 0, r = 0;
for (int i = 0; i < n; i++) {
p[i] = (i < r) ? Math.min(r - i, p[l + (r - i)]) : 0;
while (i + p[i] + 1 < n && i - p[i] - 1 >= 0 &&
t.charAt(i + p[i] + 1) == t.charAt(i - p[i] - 1)) {
p[i]++;
}
if (i + p[i] > r) {
l = i - p[i];
r = i + p[i];
}
}
return p;
}
public int countSubstrings(String s) {
int res = 0;
int[] p = manacher(s);
for (int i : p) {
res += (i + 1) / 2;
}
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
class Solution {
/**
* @param {string} s
* @return {number[]}
*/
vector<int> manacher(string& s)
{
const t = '#' + s.split('').join('#') + '#';
const n = t.length;
const p = new Array(n).fill(0);
let l = 0, r = 0;
for (let i = 0; i < n; i++) {
p[i] = (i < r) ? Math.min(r - i, p[l + (r - i)]) : 0;
while (i + p[i] + 1 < n && i - p[i] - 1 >= 0 &&
t[i + p[i] + 1] === t[i - p[i] - 1]) {
p[i]++;
}
if (i + p[i] > r) {
l = i - p[i];
r = i + p[i];
}
}
return p;
}
/**
* @param {string} s
* @return {number}
*/
countSubstrings(s) {
const p = this.manacher(s);
let res = 0;
for (let i of p) {
res += Math.floor((i + 1) / 2);
}
return res;
}
}
