题目描述
给你数字字符串s,请你返回s中长度为5的回文子序列数目。由于答案可能很大,请你将答案对109+7取余 后返回。
提示:
- 如果一个字符串从前往后和从后往前读相同,那么它是回文字符串 。
- 子序列是一个字符串中删除若干个字符后,不改变字符顺序,剩余字符构成的字符串。
样例1
输入:s = "103301"
输出:2
解释:
总共有6长度为5的子序列:"10330","10331","10301","10301","13301","03301" 。
它们中有两个(都是 "10301")是回文的。
样例2
输入:s = "9999900000"
输出:2
解释:仅有的两个回文子序列是"99999"和"00000"。
限制
- 1≤s.length≤104
- s只包含数字字符。
算法
动态规划+组合
动态规划
状态定义:f[i][x][y]表示0~i中xy出现的次数;g[i][y][x]表示i~n−1中yx出现的次数。
状态转移:
1. 如果s[i]=y则f[i][x][y]由两个部分组成,(1)[0,i)中xy的出现次数;(2)[0,i)中x的出现次数cnt,这些x可以和s[i]构成cnt个新的xy。因此,我们还需要预处理每种数字的前缀出现频数。
2. 如果s[i]≠y,则只有(1)。同理分析后缀g,得到状态转移方程如下:
f[i][x][y]=f[i−1][x][y]+c[x][i−1] ,s[i]=y
f[i][x][y]=f[i−1][x][y] ,s[i]≠y
g[i][y][x]=g[i+1][y][x]+c[x][n−1]−c[x][i] ,s[i]=y
g[i][y][x]=g[i+1][y][x] ,s[i]≠y
组合
遍历每个位置i,以这个位置为回文中心,将两边长度为2的镜像对称的子序列交叉组合起来就行了,即
res=Σn−3i=2f[i−1][x][y]×g[i+1][y][x]
复杂度分析
f数组和g数组的规模都是100n,前缀和数组c的规模是10n,额外空间复杂度为O(Σ2n),Σ为字符集大小,本题中Σ=10;
动态规划需要枚举xy,最内层还有O(n)的时间复杂度,整体时间复杂度为O(Σ2n);组合的时间复杂度也相同,因此这也是整个算法的时间复杂度。
typedef long long LL;
const int N = 1e5 + 5;
int f[N][10][10], g[N][10][10];
class Solution {
public:
const int MOD = 1e9 + 7;
int countPalindromes(string s) {
int n = s.size();
if(n < 5) return 0;
vector<int> c[10];
for(int x = 0; x <= 9; x++) {
c[x] = vector<int>(n);
c[x][0] = s[0] == (char)('0' + x);
for(int i = 1; i < n; i++) {
c[x][i] = c[x][i - 1] + (s[i] - '0' == x);
}
}
memset(f, 0, sizeof f);
memset(g, 0, sizeof g);
for(int x = 0; x <= 9; x++) {
for(int y = 0; y <= 9; y++) {
for(int i = 1; i < n; i++) {
int num = s[i] - '0';
f[i][x][y] = f[i - 1][x][y] + (num == y? c[x][i - 1]: 0);
num = s[n - 1 - i] - '0';
g[n - 1 - i][y][x] = g[n - i][y][x] + (num == y? c[x][n - 1] - c[x][n - 1 - i]: 0);
}
}
}
LL ans = 0;
for(int x = 0; x <= 9; x++) {
for(int y = 0; y <= 9; y++) {
for(int i = 2; i < n - 2; i++) {
ans = (ans + (LL)f[i - 1][x][y]*g[i + 1][y][x]) % MOD;
}
}
}
return ans;
}
};