持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第16天,点击查看活动详情
题目描述
给定一个字符串 s,返回 s 中不同的非空「回文子序列」个数 。
通过从 s 中删除 0 个或多个字符来获得子序列。
如果一个字符序列与它反转后的字符序列一致,那么它是「回文字符序列」。
如果有某个 i , 满足 ai != bi ,则两个序列 a1, a2, ... 和 b1, b2, ... 不同。
注意:
结果可能很大,你需要对 109 + 7 取模 。
示例 1:
输入:s = 'bccb'
输出:6
解释:6 个不同的非空回文子字符序列分别为:'b', 'c', 'bb', 'cc', 'bcb', 'bccb'。
注意:'bcb' 虽然出现两次但仅计数一次。
示例 2:
输入:s = 'abcdabcdabcdabcdabcdabcdabcdabcddcbadcbadcbadcbadcbadcbadcbadcba'
输出:104860361
解释:共有 3104860382 个不同的非空回文子序列,104860361 对 109 + 7 取模后的值。
提示:
- 1 <= s.length <= 1000
- s[i] 仅包含 'a', 'b', 'c' 或 'd'
思路
听说过区间DP,但是还是没写出来状态转移方程,看了官方题解和三叶姐的题解,记录一下。
定义二维数组f[][],f[i][j]代表下标在[i,j]之间的子串的不同回文个数。
可选字符只有abcd4个,可以依次枚举,计算每个字符作为边缘时对f[i][j]的贡献度,有以下几种情况:
- f[i][j]不包含字符ch,那么其中任何一个回文子串都不会包含ch,贡献度为0
- f[i][j]只包含字符ch1次,只有[ch]本身是回文,那么贡献度为1
- f[i][j]包含字符ch的次数大于等于2次,那么把ch在f[i][j]中最左和最右的位置记为left和right,
-
- 如果left+1==right,那么贡献度有[ch]、[chch],即2
-
- 如果left+1<right,那么贡献度有[ch]、[chch]、f[left+1][right-1],即 f[left+1][right-1] + 2
Java版本代码
class Solution {
private static final int MOD = 1000000007;
public int countPalindromicSubsequences(String s) {
final int CHAR_COUNT = 4;
char[] cs = s.toCharArray();
int n = cs.length;
int[][] f = new int[n][n];
int[] leftIndex = new int[CHAR_COUNT];
int[] rightIndex = new int[CHAR_COUNT];
Arrays.fill(leftIndex, -1);
for (int i = n-1; i >= 0; i--) {
leftIndex[cs[i] - 'a'] = i;
Arrays.fill(rightIndex, -1);
for (int j = i; j < n; j++) {
rightIndex[cs[j] - 'a'] = j;
for (int c = 0; c < CHAR_COUNT; c++) {
int left = leftIndex[c], right = rightIndex[c];
if (left == -1 || right == -1) {
continue;
}
if (left == right) {
f[i][j] = (f[i][j] + 1) % MOD;
} else if (left+1 == right) {
f[i][j] = (f[i][j] + 2) % MOD;
} else {
f[i][j] = (f[i][j] + f[left+1][right-1] + 2) % MOD;
}
}
}
}
return f[0][n-1];
}
}