Problem

Given a string s, return the number of different non-empty palindromic subsequences in s. Since the answer may be very large, return it modulo 10^9 + 7.

A subsequence of a string is obtained by deleting zero or more characters from the string.

A sequence is palindromic if it is equal to the sequence reversed.

Two sequences a1, a2, ... and b1, b2, ... are different if there is some i for which ai != bi.

Examples

Example 1

1
2
3
4
Input: s = "bccb"
Output: 6
Explanation: The 6 different non-empty palindromic subsequences are 'b', 'c', 'bb', 'cc', 'bcb', 'bccb'.
Note that 'bcb' is counted only once, even though it occurs twice.

Example 2

1
2
3
Input: s = "abcdabcdabcdabcdabcdabcdabcdabcddcbadcbadcbadcbadcbadcbadcbadcba"
Output: 104860361
Explanation: There are 3104860382 different non-empty palindromic subsequences, which is 104860361 modulo 10^9 + 7.

Constraints

  • 1 <= s.length <= 1000
  • s[i] is either 'a''b''c', or 'd'.

Solution

Method 1 – Top-Down Dynamic Programming with Memoization

Intuition

The problem asks for the count of distinct palindromic subsequences. Since subsequences can overlap and repeat, we need to avoid double-counting. By using DP with memoization, we can efficiently count unique palindromic subsequences in all substrings, leveraging overlapping subproblems.

Approach

  1. Define a recursive function dp(l, r) that returns the number of distinct palindromic subsequences in the substring s[l:r+1].
  2. Use memoization to cache results for each (l, r) to avoid recomputation.
  3. For each character ch in 'a'..'d':
  • Find the first (i) and last (j) occurrence of ch in s[l:r+1].
  • If i > j, skip (character not present).
  • If i == j, only one palindromic subsequence: the single character.
  • If i < j, count two palindromic subsequences (ch and ch...ch), plus all palindromic subsequences inside s[i+1:j].
  1. Sum the counts for all characters and return the result modulo 10^9+7.
  2. Base case: if l > r, return 0.

Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
   int countPalindromicSubsequences(std::string s) {
      const int mod = 1e9 + 7, n = s.size();
      std::vector<std::vector<int>> memo(n, std::vector<int>(n, -1));
      std::function<int(int, int)> dp = [&](int l, int r) -> int {
        if (l > r) return 0;
        if (memo[l][r] != -1) return memo[l][r];
        int ans = 0;
        for (char ch = 'a'; ch <= 'd'; ++ch) {
           int i = l, j = r;
           while (i <= r && s[i] != ch) ++i;
           while (j >= l && s[j] != ch) --j;
           if (i > j) continue;
           if (i == j) ans = (ans + 1) % mod;
           else ans = (ans + 2 + dp(i + 1, j - 1)) % mod;
        }
        return memo[l][r] = ans;
      };
      return dp(0, n - 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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
func countPalindromicSubsequences(s string) int {
   mod := int(1e9 + 7)
   n := len(s)
   memo := make([][]int, n)
   for i := range memo {
      memo[i] = make([]int, n)
      for j := range memo[i] {
        memo[i][j] = -1
      }
   }
   var dp func(int, int) int
   dp = func(l, r int) int {
      if l > r {
        return 0
      }
      if memo[l][r] != -1 {
        return memo[l][r]
      }
      ans := 0
      for ch := byte('a'); ch <= 'd'; ch++ {
        i, j := l, r
        for i <= r && s[i] != ch {
           i++
        }
        for j >= l && s[j] != ch {
           j--
        }
        if i > j {
           continue
        }
        if i == j {
           ans = (ans + 1) % mod
        } else {
           ans = (ans + 2 + dp(i+1, j-1)) % mod
        }
      }
      memo[l][r] = ans
      return ans
   }
   return dp(0, n-1)
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
   public int countPalindromicSubsequences(String s) {
      int mod = 1000000007, n = s.length();
      Integer[][] memo = new Integer[n][n];
      return dp(0, n - 1, s, memo, mod);
   }
   private int dp(int l, int r, String s, Integer[][] memo, int mod) {
      if (l > r) return 0;
      if (memo[l][r] != null) return memo[l][r];
      int ans = 0;
      for (char ch = 'a'; ch <= 'd'; ch++) {
        int i = l, j = r;
        while (i <= r && s.charAt(i) != ch) i++;
        while (j >= l && s.charAt(j) != ch) j--;
        if (i > j) continue;
        if (i == j) ans = (ans + 1) % mod;
        else ans = (ans + 2 + dp(i + 1, j - 1, s, memo, mod)) % mod;
      }
      memo[l][r] = ans;
      return ans;
   }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
   fun countPalindromicSubsequences(s: String): Int {
      val mod = 1_000_000_007
      val n = s.length
      val memo = Array(n) { IntArray(n) { -1 } }
      fun dp(l: Int, r: Int): Int {
        if (l > r) return 0
        if (memo[l][r] != -1) return memo[l][r]
        var ans = 0
        for (ch in 'a'..'d') {
           var i = l
           var j = r
           while (i <= r && s[i] != ch) i++
           while (j >= l && s[j] != ch) j--
           if (i > j) continue
           if (i == j) ans = (ans + 1) % mod
           else ans = (ans + 2 + dp(i + 1, j - 1)) % mod
        }
        memo[l][r] = ans
        return ans
      }
      return dp(0, n - 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
25
26
class Solution:
   def countPalindromicSubsequences(self, s: str) -> int:
      mod: int = 10 ** 9 + 7
      n: int = len(s)
      memo: list[list[int]] = [[-1] * n for _ in range(n)]
      def dp(l: int, r: int) -> int:
        if l > r:
           return 0
        if memo[l][r] != -1:
           return memo[l][r]
        ans: int = 0
        for ch in 'abcd':
           i, j = l, r
           while i <= r and s[i] != ch:
              i += 1
           while j >= l and s[j] != ch:
              j -= 1
           if i > j:
              continue
           if i == j:
              ans = (ans + 1) % mod
           else:
              ans = (ans + 2 + dp(i + 1, j - 1)) % mod
        memo[l][r] = ans
        return ans
      return dp(0, n - 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
25
26
27
28
29
30
31
32
33
impl Solution {
   pub fn count_palindromic_subsequences(s: String) -> i32 {
      const MOD: i32 = 1_000_000_007;
      let n = s.len();
      let s = s.as_bytes();
      let mut memo = vec![vec![-1; n]; n];
      fn dp(l: usize, r: usize, s: &[u8], memo: &mut Vec<Vec<i32>>) -> i32 {
        if l > r { return 0; }
        if memo[l][r] != -1 { return memo[l][r]; }
        let mut ans = 0;
        for &ch in b"abcd" {
           let mut i = l;
           let mut j = r;
           while i <= r && s[i] != ch { i += 1; }
           while j >= l && s[j] != ch {
              if j == 0 { break; }
              j -= 1;
           }
           if i > j { continue; }
           if i == j { ans = (ans + 1) % MOD; }
           else {
              let inner = if i + 1 <= j.saturating_sub(1) {
                dp(i + 1, j - 1, s, memo)
              } else { 0 };
              ans = (ans + 2 + inner) % MOD;
           }
        }
        memo[l][r] = ans;
        ans
      }
      dp(0, n - 1, s, &mut memo)
   }
}

Complexity

  • ⏰ Time complexity: O(n^3)
  • 🧺 Space complexity: O(n^2)

Method 2 – Bottom-Up Dynamic Programming (Tabulation)

Intuition: Instead of solving subproblems recursively, we can fill a DP table iteratively for all substrings. For each substring, we count palindromic subsequences by considering the contribution of each character and using previously computed results for smaller substrings.

Approach:

  1. Let dp[i][j] be the number of different palindromic subsequences in s[i..j].
  2. Initialize dp[i][i] = 1 for all i (single characters are palindromic).
  3. For substrings of increasing length (len from 2 to n):
  • For each substring s[i..j]:
    • For each character ch in 'a'..'d':
    • Find the first (l) and last (r) occurrence of ch in s[i..j].
    • If l > r, skip.
    • If l == r, add 1 (single character).
    • If l < r, add 2 (for ch and ch...ch) plus dp[l+1][r-1] (palindromes inside).
    • Sum for all ch and take modulo.
  1. Return dp[0][n-1] as the answer.

Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
   int countPalindromicSubsequences(std::string s) {
      int n = s.size(), mod = 1e9 + 7;
      std::vector<std::vector<int>> dp(n, std::vector<int>(n, 0));
      for (int i = 0; i < n; ++i) dp[i][i] = 1;
      for (int len = 2; len <= n; ++len) {
        for (int i = 0; i + len - 1 < n; ++i) {
           int j = i + len - 1, ans = 0;
           for (char ch = 'a'; ch <= 'd'; ++ch) {
              int l = i, r = j;
              while (l <= j && s[l] != ch) ++l;
              while (r >= i && s[r] != ch) --r;
              if (l > r) continue;
              if (l == r) ans = (ans + 1) % mod;
              else ans = (ans + 2 + dp[l + 1][r - 1]) % mod;
           }
           dp[i][j] = ans;
        }
      }
      return dp[0][n - 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
25
26
27
28
29
30
31
32
33
34
35
36
func countPalindromicSubsequences(s string) int {
   n, mod := len(s), int(1e9+7)
   dp := make([][]int, n)
   for i := range dp {
      dp[i] = make([]int, n)
      dp[i][i] = 1
   }
   for l := 2; l <= n; l++ {
      for i := 0; i+l-1 < n; i++ {
        j, ans := i+l-1, 0
        for ch := byte('a'); ch <= 'd'; ch++ {
           left, right := i, j
           for left <= j && s[left] != ch {
              left++
           }
           for right >= i && s[right] != ch {
              right--
           }
           if left > right {
              continue
           }
           if left == right {
              ans = (ans + 1) % mod
           } else {
              inner := 0
              if left+1 <= right-1 {
                inner = dp[left+1][right-1]
              }
              ans = (ans + 2 + inner) % mod
           }
        }
        dp[i][j] = ans
      }
   }
   return dp[0][n-1]
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
   public int countPalindromicSubsequences(String s) {
      int n = s.length(), mod = 1000000007;
      int[][] dp = new int[n][n];
      for (int i = 0; i < n; i++) dp[i][i] = 1;
      for (int len = 2; len <= n; len++) {
        for (int i = 0; i + len - 1 < n; i++) {
           int j = i + len - 1, ans = 0;
           for (char ch = 'a'; ch <= 'd'; ch++) {
              int l = i, r = j;
              while (l <= j && s.charAt(l) != ch) l++;
              while (r >= i && s.charAt(r) != ch) r--;
              if (l > r) continue;
              if (l == r) ans = (ans + 1) % mod;
              else ans = (ans + 2 + dp[l + 1][r - 1]) % mod;
           }
           dp[i][j] = ans;
        }
      }
      return dp[0][n - 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
25
class Solution {
   fun countPalindromicSubsequences(s: String): Int {
      val n = s.length
      val mod = 1_000_000_007
      val dp = Array(n) { IntArray(n) }
      for (i in 0 until n) dp[i][i] = 1
      for (len in 2..n) {
        for (i in 0..n - len) {
           val j = i + len - 1
           var ans = 0
           for (ch in 'a'..'d') {
              var l = i
              var r = j
              while (l <= j && s[l] != ch) l++
              while (r >= i && s[r] != ch) r--
              if (l > r) continue
              if (l == r) ans = (ans + 1) % mod
              else ans = (ans + 2 + dp[l + 1][r - 1]) % mod
           }
           dp[i][j] = ans
        }
      }
      return dp[0][n - 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
25
26
class Solution:
   def countPalindromicSubsequences(self, s: str) -> int:
      n: int = len(s)
      mod: int = 10 ** 9 + 7
      dp: list[list[int]] = [[0] * n for _ in range(n)]
      for i in range(n):
        dp[i][i] = 1
      for length in range(2, n + 1):
        for i in range(n - length + 1):
           j: int = i + length - 1
           ans: int = 0
           for ch in 'abcd':
              l, r = i, j
              while l <= j and s[l] != ch:
                l += 1
              while r >= i and s[r] != ch:
                r -= 1
              if l > r:
                continue
              if l == r:
                ans = (ans + 1) % mod
              else:
                inner = dp[l + 1][r - 1] if l + 1 <= r - 1 else 0
                ans = (ans + 2 + inner) % mod
           dp[i][j] = ans
      return dp[0][n - 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
25
26
27
28
29
30
31
32
33
34
35
36
37
impl Solution {
   pub fn count_palindromic_subsequences(s: String) -> i32 {
      const MOD: i32 = 1_000_000_007;
      let n = s.len();
      let s = s.as_bytes();
      let mut dp = vec![vec![0; n]; n];
      for i in 0..n {
        dp[i][i] = 1;
      }
      for len in 2..=n {
        for i in 0..=n - len {
           let j = i + len - 1;
           let mut ans = 0;
           for &ch in b"abcd" {
              let mut l = i;
              let mut r = j;
              while l <= j && s[l] != ch { l += 1; }
              while r >= i && s[r] != ch {
                if r == 0 { break; }
                r -= 1;
              }
              if l > r { continue; }
              if l == r {
                ans = (ans + 1) % MOD;
              } else {
                let inner = if l + 1 <= r.saturating_sub(1) {
                   dp[l + 1][r - 1]
                } else { 0 };
                ans = (ans + 2 + inner) % MOD;
              }
           }
           dp[i][j] = ans;
        }
      }
      dp[0][n - 1]
   }
}

Complexity

  • ⏰ Time complexity: O(N^3)
  • 🧺 Space complexity: O(N^2)