这道题的解法简直堪称天才。
给定字符串S长度在[1, 1000],字符属于集合[‘a’, ‘b’, ‘c’, ‘d’],定义回文子串为删去其中0个或更多个字符得到的回文字符串。求不同的回文子串数?(mod 1000000007)
如字符串’abcdabcdabcdabcdabcdabcdabcdabcddcbadcbadcbadcbadcbadcbadcbadcba’的回文子串数mod1000000007为104860361
如果要找出所有不重复的回文子串,那么长度为1000的字符串有4^1000-1个子串,大大超出了内存的容量。且判定是否回文花费更多的时间。直接上评论区的代码:
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];
}
}
以及评论区的优化:
A minor optimization, preprocess the string and keep rightNext and leftNext in two arrays of int.
For every dp[i][j], we do not have to calculate low and high which costs O(n).
The following optimization can cut the time complexity from O(n ^ 3) to O(n ^ 2)
int countPalindromicSubsequences(string S) {
int n=S.size();
long long mod=1e9+7;
vector<vector<long long>> dp(n,vector<long long> (n,0));
for(int i=0;i<n;i++)
dp[i][i]=1;
for(int len=1;len<n;len++)
{
for(int i=0;i+len<n;i++)
{
int j=i+len;
if(S[i]==S[j])
{
int left=i+1, right=j-1;
while(left<=right && S[left]!=S[i])
left++;
while(left<=right && S[right]!=S[i])
right--;
if(left>right)
dp[i][j]=dp[i+1][j-1]*2+2;
else if(left==right)
dp[i][j]=dp[i+1][j-1]*2+1;
else
dp[i][j]=dp[i+1][j-1]*2-dp[left+1][right-1];
}
else
dp[i][j]=dp[i+1][j]+dp[i][j-1]-dp[i+1][j-1];
dp[i][j] = dp[i][j] < 0? dp[i][j] + mod:dp[i][j]% mod;
}
}
return (int)dp[0][n-1];
}
变成JAVA代码:
public int countPalindromicSubsequences(String S) {
int n = S.length();
char[] s = S.toCharArray();
int[] lastPos = new int[4], leftNext = new int[n], rightNext = new int[n];
for (int i = 0; i < 4; i++) {
lastPos[i] = -1;
}
for (int i = 0; i < n; i++) {
int c = s[i] - 'a';
leftNext[i] = lastPos[c];
lastPos[c] = i;
}
for (int i = 0; i < 4; i++) {
lastPos[i] = -1;
}
for (int i = n - 1; i >= 0; i--) {
int c = s[i] - 'a';
rightNext[i] = lastPos[c];
lastPos[c] = i;
}
int[][] re = new int[n][n];
for (int i = 0; i < n - 1; i++) {
re[i][i] = 1;
re[i][i + 1] = 2;
}
re[n - 1][n - 1] = 1;
for (int x = 2; x < n; x++) {
for (int i = 0; i + x < n; i++) {
int j = x + i, r = rightNext[i], l = leftNext[j];
if (s[i] != s[j]) {
re[i][j] = re[i + 1][j] + re[i][j - 1] - re[i + 1][j - 1];
}
else {
if (r == -1 || r == j) {
re[i][j] = 2 * re[i + 1][j - 1] + 2;
}
else {
if (r == l) {
re[i][j] = 2 * re[i + 1][j - 1] + 1;
}
else {
re[i][j] = 2 * re[i + 1][j - 1] - re[r + 1][l - 1];
}
}
}
re[i][j] = Math.floorMod(re[i][j], 1000000007);
}
}
return re[0][n - 1];
}
这种题只有天才才能自己想出来解法