LeetCode 2484 - Count Palindromic Subsequences
The problem asks us to count all palindromic subsequences of length 5 within a given string of digits s. A palindromic subsequence is a sequence that reads the same forward and backward, and a subsequence can be formed by deleting zero or more characters without changing the…
Difficulty: 🔴 Hard
Topics: String, Dynamic Programming
Solution
Problem Understanding
The problem asks us to count all palindromic subsequences of length 5 within a given string of digits s. A palindromic subsequence is a sequence that reads the same forward and backward, and a subsequence can be formed by deleting zero or more characters without changing the order of the remaining characters. For example, given "103301", one valid palindromic subsequence of length 5 is "10301".
The input s is a string of digits with length up to 10,000. The output is the count of palindromic subsequences modulo 10^9 + 7.
Important constraints and implications:
1 <= s.length <= 10^4implies that a brute-force enumeration of all subsequences is infeasible because there are combinatorially many (O(n^5) for length-5 subsequences).- Only digits are allowed (
0-9), which is a small character set. This allows us to optimize using frequency arrays or prefix/suffix counting. - Edge cases to watch out for include strings shorter than 5 (where the answer is 0), strings with repeated identical digits (which can form multiple palindromes), and strings with no palindromes at all.
Approaches
Brute-Force Approach: Enumerate all combinations of 5 indices from the string and check if the selected characters form a palindrome. This works correctly but is extremely slow because choosing 5 indices from n has O(n^5) complexity, which is infeasible for n = 10,000.
Optimal Approach: The key insight is that a palindromic subsequence of length 5 has the form a b c b a. This means the first and last characters must be the same, the second and fourth must be the same, and the middle character can be any digit. We can optimize by counting pairs of digits efficiently using prefix and suffix frequency arrays.
- Precompute the count of single digits and pairs of digits from left and right.
- Iterate over all possible middle positions
ifor the palindrome. For each position, count how many valida bpairs exist on the left and right. - Multiply the number of left pairs by the number of right pairs for each
iand sum to get the total.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n^5) | O(1) | Enumerate all 5-length subsequences and check palindromicity |
| Optimal | O(n * 100) ≈ O(n) | O(100) | Count pairs using prefix and suffix frequency arrays for each middle position |
Algorithm Walkthrough
- Initialize two 2D arrays
leftandrightof size10 x 10to store counts of digit pairs(a, b)to the left and right of each position. - Initialize
freqLeftandfreqRightarrays of size 10 to store counts of single digits to the left and right. - Fill
rightby iterating from the end of the string to the beginning. For each character, updaterightusing the frequency of digits seen so far on the right. Increment the count for each(current_digit, other_digit)pair. UpdatefreqRightaccordingly. - Iterate from left to right. For each position
ias the middle character of the palindrome, multiply the count of pairs(a, b)to the left by the count of pairs(a, b)to the right to get the number of palindromes centered ati. Accumulate this count. - Update
freqLeftandleftarrays for the next iteration.
Why it works: By precomputing the number of valid (a, b) pairs to the left and right of each potential middle character, we ensure that every valid palindrome a b c b a is counted exactly once. The use of prefix and suffix frequency arrays guarantees correctness.
Python Solution
class Solution:
def countPalindromes(self, s: str) -> int:
MOD = 10**9 + 7
n = len(s)
if n < 5:
return 0
left = [[0]*10 for _ in range(10)]
right = [[0]*10 for _ in range(10)]
freqLeft = [0]*10
freqRight = [0]*10
# Fill right pair counts
for ch in s:
d = int(ch)
for i in range(10):
right[i][d] += freqRight[i]
freqRight[d] += 1
result = 0
freqLeft = [0]*10
for ch in s:
d = int(ch)
freqRight[d] -= 1
for i in range(10):
right[i][d] -= freqRight[i]
for a in range(10):
for b in range(10):
result = (result + left[a][b] * right[a][b]) % MOD
for i in range(10):
left[i][d] += freqLeft[i]
freqLeft[d] += 1
return result
The implementation divides the counting into three phases: building the right pairs, iterating through middle positions and combining left and right counts, and updating left pairs. This ensures that all palindromic subsequences are counted efficiently without missing any or double-counting.
Go Solution
func countPalindromes(s string) int {
const MOD = 1_000_000_007
n := len(s)
if n < 5 {
return 0
}
left := [10][10]int{}
right := [10][10]int{}
freqLeft := [10]int{}
freqRight := [10]int{}
for _, ch := range s {
d := int(ch - '0')
for i := 0; i < 10; i++ {
right[i][d] += freqRight[i]
}
freqRight[d]++
}
result := 0
for _, ch := range s {
d := int(ch - '0')
freqRight[d]--
for i := 0; i < 10; i++ {
right[i][d] -= freqRight[i]
}
for a := 0; a < 10; a++ {
for b := 0; b < 10; b++ {
result = (result + left[a][b]*right[a][b]) % MOD
}
}
for i := 0; i < 10; i++ {
left[i][d] += freqLeft[i]
}
freqLeft[d]++
}
return result
}
The Go version mirrors the Python logic. Arrays are fixed-size for digits 0-9, integer overflow is handled naturally using modulo arithmetic, and type conversion is explicit for character digits.
Worked Examples
Example 1: s = "103301"
leftpairs after first character: empty.rightpairs: counts pairs like(1,0),(0,3),(3,3)etc.- Middle character
3:left[a][b] * right[a][b]counts exactly 2 palindromes (10301twice).
Example 2: s = "0000000"
- Every combination of 5 zeros forms a palindrome.
leftandrightpair counting ensures all 21 subsequences counted.
Example 3: s = "9999900000"
- Only palindromes are
99999and00000. - Counting pairs accurately returns 2.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n*100) ≈ O(n) | Iterate over string for each middle character, nested loop over 10x10 digit pairs |
| Space | O(100) ≈ O(1) | Fixed-size arrays for digit pairs and frequencies |
The algorithm scales linearly with the string length because the inner loops are constant (10x10), making it efficient for n up to 10,000.
Test Cases
sol = Solution()
# Provided examples
assert sol.countPalindromes("103301") == 2 # Example 1
assert sol.countPalindromes("0000000") == 21 # Example 2
assert sol.countPalindromes("9999900000") == 2 # Example 3
# Boundary cases
assert sol.countPalindromes("") == 0 # Empty string
assert sol.countPalindromes("1234") == 0 # Less than 5 characters
assert sol.countPalindromes("11111") == 1 # Single palindrome of repeated digit
assert sol.countPalindromes("1234512345") > 0 # Multiple palindromes with different digits
| Test | Why |
|---|---|
| "" | Tests empty input |
| "1234" | Tests input shorter than 5 characters |
| "11111" | Tests input with exactly one palindrome |
| "103301" | Tests example with repeated palindromes |
| "0000000" | Tests repeated identical digit subsequences |
| "1234512345" | Tests |