Given a string S, find the number of different non-empty palindromic subsequences in S, and return that number modulo 10^9 + 7
.
A subsequence of a string S is obtained by deleting 0 or more characters from S.
A sequence is palindromic if it is equal to the sequence reversed.
Two sequences A_1, A_2, ...
and B_1, B_2, ...
are different if there is some i
for which A_i != B_i
.
Example 1:
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:
Input:
S = 'abcdabcdabcdabcdabcdabcdabcdabcddcbadcbadcbadcbadcbadcbadcbadcba'
Output: 104860361
Explanation:
There are 3104860382 different non-empty palindromic subsequences, which is 104860361 modulo 10^9 + 7.
Note:
The length of S
will be in the range [1, 1000]
.Each character S[i]
will be in the set {'a', 'b', 'c', 'd'}
.
先参照discuss里面的memo方法写了一个,有dp方案,以后有时间在写
package l730;
import java.util.TreeSet;
class Solution {
public int countPalindromicSubsequences(String S) {
char[] cs = S.toCharArray();
TreeSet[] ts = new TreeSet[26];
// Arrays.fill(ts, new TreeSet<Integer>()); // 数组每个treeset是同一个treeset
for(int i=0; i<26; i++) ts[i] = new TreeSet<Integer>();
for(int i=0; i<cs.length; i++)
ts[cs[i]-'a'].add(i);
Integer[][] dp = new Integer[cs.length][cs.length]; // dp值可能默认就是0,所以这里用Integer数组(默认为null)
return memo(cs, dp, ts, 0, cs.length-1);
}
/*
* 每次调用一次memo就必须选定一层回文char对应
* 可以确保不出现重复
*/
private int memo(char[] cs, Integer[][] dp, TreeSet[] ts, int s, int t) {
if(s > t) return 0;
if(dp[s][t] != null) return dp[s][t];
long ret = 0;
for(int i=0; i<26; i++) {
Integer newS = (Integer) ts[i].ceiling(s);
Integer newT = (Integer) ts[i].floor(t);
if(newS == null || newT == null || newS > newT) continue;
ret ++; // self alone
if(newS != newT) ret++; // pair
ret += memo(cs, dp, ts, newS+1, newT-1);
}
dp[s][t] = (int) (ret%1000000007);
return dp[s][t];
}
}
I am not able to pass this question one time but struggle a lot in the basic test cases like "a", "aa", "aaa", "aba", "aabb". Those test cases help my early rough idea to be flawless. The basic idea of DP is easy to understand, I maintain DP[i][j] to record in substring from i to j(included), the number of palindrome without duplicate. Then we consider two cases of the DP equation:
when s.charAt(i) != s.charAt(j):
dp[i][j] = dp[i][j] = dp[i][j - 1] + dp[i + 1][j] - dp[i + 1][j - 1];
When s.charAt(i) == s.charAt(j):
the situation get much more complex and I fix a lot the wrong answers. I have comment the branches where which kind of test cases are considered.
class Solution {
public int countPalindromicSubsequences(String s) {
int len = s.length();
int[][] dp = new int[len][len];
char[] chs = s.toCharArray();
for(int i = 0; i < len; i++){
dp[i][i] = 1; // Consider the test case "a", "b" "c"...
}
for(int distance = 1; distance < len; distance++){
for(int i = 0; i < len - distance; i++){
int j = i + distance;
if(chs[i] == chs[j]){
int low = i + 1;
int high = j - 1;
/* Variable low and high here are used to get rid of the duplicate*/
while(low <= high && chs[low] != chs[j]){
low++;
}
while(low <= high && chs[high] != chs[j]){
high--;
}
if(low > high){
// consider the string from i to j is "a...a" "a...a"... where there is no character 'a' inside the leftmost and rightmost 'a'
/* eg: "aba" while i = 0 and j = 2: dp[1][1] = 1 records the palindrome{"b"},
the reason why dp[i + 1][j - 1] * 2 counted is that we count dp[i + 1][j - 1] one time as {"b"},
and additional time as {"aba"}. The reason why 2 counted is that we also count {"a", "aa"}.
So totally dp[i][j] record the palindrome: {"a", "b", "aa", "aba"}.
*/
dp[i][j] = dp[i + 1][j - 1] * 2 + 2;
}
else if(low == high){
// consider the string from i to j is "a...a...a" where there is only one character 'a' inside the leftmost and rightmost 'a'
/* eg: "aaa" while i = 0 and j = 2: the dp[i + 1][j - 1] records the palindrome {"a"}.
the reason why dp[i + 1][j - 1] * 2 counted is that we count dp[i + 1][j - 1] one time as {"a"},
and additional time as {"aaa"}. the reason why 1 counted is that
we also count {"aa"} that the first 'a' come from index i and the second come from index j. So totally dp[i][j] records {"a", "aa", "aaa"}
*/
dp[i][j] = dp[i + 1][j - 1] * 2 + 1;
}
else{
// consider the string from i to j is "a...a...a... a" where there are at least two character 'a' close to leftmost and rightmost 'a'
/* eg: "aacaa" while i = 0 and j = 4: the dp[i + 1][j - 1] records the palindrome {"a", "c", "aa", "aca"}.
the reason why dp[i + 1][j - 1] * 2 counted is that we count dp[i + 1][j - 1] one time as {"a", "c", "aa", "aca"},
and additional time as {"aaa", "aca", "aaaa", "aacaa"}. Now there is duplicate : {"aca"},
which is removed by deduce dp[low + 1][high - 1]. So totally dp[i][j] record {"a", "c", "aa", "aca", "aaa", "aaaa", "aacaa"}
*/
dp[i][j] = dp[i + 1][j - 1] * 2 - dp[low + 1][high - 1];
}
}
else{
dp[i][j] = dp[i][j - 1] + dp[i + 1][j] - dp[i + 1][j - 1]; //s.charAt(i) != s.charAt(j)
}
dp[i][j] = dp[i][j] < 0 ? dp[i][j] + 1000000007 : dp[i][j] % 1000000007;
}
}
return dp[0][len - 1];
}
}