String · 22 of 43
Count All Palindromic Subsequence in a given String
Count every way to pick some letters, in order, that reads the same forwards and backwards, by building answers for short stretches first.
- Simple way
- time O(2^n * n)
room O(n) - Clever way
- time O(n^2)
room O(n^2)
The problem, simply
A subsequence is what you get when you cross out some letters of a string and keep the rest in order. From abcb you can get ab, acb, bb and so on.
Count how many subsequences are palindromes, meaning they read the same both ways. Picks that use different spots count separately, even if they spell the same word. So in aab, the two single as count as two. The answer can be huge, so it is usually asked modulo 10^9 + 7.
A story to remember it
Picture it
The string abcb, and the chart we will fill. Box (i, j) answers the question for the letters from spot i to spot j. The top right box is the whole string.
- answer
Examples
A normal case
- In
s = 'abcb'- Out
6
a, b, c, b, bb and bcb. The two b's count separately because they sit in different spots.
All different letters
- In
s = 'abcd'- Out
4
Only the four single letters. No two letters match, so nothing longer works.
Repeated letters
- In
s = 'aab'- Out
4
a (first), a (second), b, and aa.
All the same
- In
s = 'aaa'- Out
7
Every non-empty pick is a palindrome: 3 singles, 3 pairs and 1 triple. That is 2^3 - 1.
One letter
- In
s = 'a'- Out
1
A single letter is a palindrome.
Way 1: the simple way
List every pick. A string of n letters has 2^n - 1 non-empty picks, because each letter is either in or out. Check each one.
- For every number
maskfrom 1 to2^n - 1, keep letterkwhen bitkofmaskis on. - Check if that pick is a palindrome with two fingers.
- Count the ones that are.
All 15 picks of abcb. Only 6 are palindromes. With 30 letters there would be over a billion picks.
- answer
class Solution {
public long countPS(String s) {
int n = s.length();
long count = 0;
for (int mask = 1; mask < (1 << n); mask++) { // every pick
StringBuilder pick = new StringBuilder();
for (int k = 0; k < n; k++) {
if ((mask & (1 << k)) != 0) pick.append(s.charAt(k));
}
if (isPal(pick)) count++;
}
return count;
}
private boolean isPal(StringBuilder t) {
for (int l = 0, r = t.length() - 1; l < r; l++, r--) {
if (t.charAt(l) != t.charAt(r)) return false;
}
return true;
}
}How long it takes: 2^n picks, each checked in O(n), so O(2^n * n). Extra room: one pick at a time, O(n).
Way 2: the clever way
Let count(i, j) be the number of palindromic picks that use only letters from spot i to spot j. One letter alone gives 1. For a longer stretch, look at its two end letters.
- If the ends differ, no palindrome can use both ends. Every palindrome skips the left end or skips the right end. Add both counts, then take away the ones that skip both ends, because they were counted twice:
count(i, j) = count(i+1, j) + count(i, j-1) - count(i+1, j-1). - If the ends match, do the same, but now each middle palindrome also gets a twin wrapped between the two end letters, and the two ends alone make one more. The twins exactly pay back what we would take away:
count(i, j) = count(i+1, j) + count(i, j-1) + 1.
Fill the chart from short stretches to long ones. The answer is in the top right box.
Filling the chart for abcb, shortest stretches first
01 / 09
Each box (i, j) will hold how many palindromes we can pick using only the letters from spot i to spot j. Boxes below the slant are not used.
- checking
- done
- answer
- rejected
- just added
class Solution {
static final long MOD = 1_000_000_007L;
public long countPS(String s) {
int n = s.length();
if (n == 0) return 0;
long[][] dp = new long[n][n];
for (int i = 0; i < n; i++) dp[i][i] = 1; // one letter alone
for (int len = 2; len <= n; len++) { // short stretches first
for (int i = 0; i + len - 1 < n; i++) {
int j = i + len - 1;
if (s.charAt(i) == s.charAt(j)) {
dp[i][j] = (dp[i + 1][j] + dp[i][j - 1] + 1) % MOD;
} else {
long middle = (len > 2) ? dp[i + 1][j - 1] : 0;
// add MOD before % so the answer never goes negative
dp[i][j] = ((dp[i + 1][j] + dp[i][j - 1] - middle) % MOD + MOD) % MOD;
}
}
}
return dp[0][n - 1];
}
}How long it takes: the chart has about n^2 / 2 boxes and each takes a constant number of steps, O(n^2). Extra room: the chart, O(n^2).
Watch out for
- Do not forget the
+ 1in the matching case. It counts the pair of end letters alone, likebb. - The chart must be filled by length, or from the bottom row up. Filling it row by row from the top reads boxes that are still empty.
- When the stretch has length 2, the middle is empty. Use 0 for it, not
dp[i+1][i]from a random place. - Counting distinct palindromes is a different, harder problem. Ask which one is wanted.
What interviewers ask next
- Count only distinct palindromic subsequences? For matching ends you must look for the same letter inside the stretch and subtract the ones that would repeat. That is LeetCode 730, still
O(n^2)with care. - Longest palindromic subsequence instead of the count? Same chart shape: matching ends give
2 + inner, otherwise the bigger of the two neighbors. - Count palindromic substrings (no gaps allowed)? Grow outwards from each of the
2n - 1middles and count while the ends match,O(n^2)time andO(1)room. - Can you save room? Fill the chart from the bottom row up. Each row only needs the row below it, so two rows of length
nare enough,O(n)room.
Remember this
Grown-up words: a pick of letters in order is a subsequence, the chart is a dynamic programming table, filling short stretches first is interval DP, adding two counts and removing the overlap is inclusion and exclusion, and 10^9 + 7 is the usual modulus.