每日一题做题记录,参考官方和三叶的题解 |
题目要求
思路:动态规划
- 从小区间扩展至整个区间,所以区间DP;
- 定义 f [ i ] [ j ] f[i][j] f[i][j]为当前区间范围 [ i , j ] [i,j] [i,j]内回文子序列的个数,所求为整个区间内子序列个数,即返回 f [ 0 ] [ n − 1 ] f[0][n-1] f[0][n−1]。
- 因为只有四个字母,所以枚举来统计,设当前枚举字符为
k
k
k;
- 找每个小区间里最靠边的两个字符,根据二者位置关系进行不同的处理。
Java
class Solution {
int MOD = (int)1e9+7;
public int countPalindromicSubsequences(String s) {
int n = s.length();
int[][] f = new int[n][n];
int[] left = new int[4], right = new int[4]; // 最左最右的abcd
Arrays.fill(left, -1);
for(int i = n - 1; i >= 0; i--) { // 从后向前枚举、找最左
left[s.charAt(i) - 'a'] = i;
Arrays.fill(right, -1);
for(int j = i; j < n; j++) { // 从前向后枚举、找最右
right[s.charAt(j) - 'a'] = j;
for(int k = 0; k < 4; k++) { // 当前枚举字符k
if(left[k] == -1 || right[k] == -1) // 没有该字符
continue;
int l = left[k], r = right[k];
if(l == r) // 只有一个,k
f[i][j] = (f[i][j] + 1) % MOD;
else if(l == r - 1) // 相邻,两个,k和kk
f[i][j] = (f[i][j] + 2) % MOD;
else
f[i][j] = (f[i][j] + f[l + 1][r - 1] + 2) % MOD; // 小区间和当前贡献
}
}
}
return f[0][n - 1];
}
}
- 时间复杂度: O ( C × n 2 ) O(C\times n^2) O(C×n2)
- 空间复杂度: O ( n 2 ) O(n^2) O(n2)
C++
class Solution {
int MOD = (int)1e9+7;
public:
int countPalindromicSubsequences(string s) {
int n = s.size();
int f[n][n];
int left[4], right[4]; // 最左最右的abcd
memset(f, 0, sizeof(f));
memset(left, -1, sizeof(left));
for(int i = n - 1; i >= 0; i--) { // 从后向前枚举、找最左
left[s[i] - 'a'] = i;
memset(right, -1, sizeof(right));
for(int j = i; j < n; j++) { // 从前向后枚举、找最右
right[s[j] - 'a'] = j;
for(int k = 0; k < 4; k++) { // 当前枚举字符k
if(left[k] == -1 || right[k] == -1) // 没有该字符
continue;
int l = left[k], r = right[k];
if(l == r) // 只有一个,k
f[i][j] = (f[i][j] + 1) % MOD;
else if(l == r - 1) // 相邻,两个,k和kk
f[i][j] = (f[i][j] + 2) % MOD;
else
f[i][j] = (f[i][j] + f[l + 1][r - 1] + 2) % MOD; // 小区间和当前贡献
}
}
}
return f[0][n - 1];
}
};
- 时间复杂度: O ( C × n 2 ) O(C\times n^2) O(C×n2)
- 空间复杂度: O ( n 2 ) O(n^2) O(n2)
Rust
- 因为rust里面字符不好转为索引【比如
left[usize::from(s[i] - b'a')] = i;
】所以就换了个实现方法,干脆把 f f f搞成三维的,大体思路是相似的。
impl Solution {
pub fn count_palindromic_subsequences(s: String) -> i32 {
let s = s.as_bytes();
let n = s.len();
let mut f = vec![vec![vec![0; 4]; n]; n];
let MOD = 1000000007;
for i in (0..n).rev() { // 从后向前
for j in i..n { // 从前向后
for k in 0..4 {
let c = b'a' + k as u8; // 当前枚举字符c
if i == j { // 只有一个
if s[i] == c {
f[i][j][k] = 1
}
}
else {
if s[i] != c { // 左不对,右移
f[i][j][k] = f[i + 1][j][k];
}
else if s[j] != c { // 右不对,左移
f[i][j][k] = f[i][j - 1][k];
}
else {
if j == i + 1 { // 相邻
f[i][j][k] = 2;
}
else {
f[i][j][k] = 2; // 当前贡献,c和cc
for m in 0..4 { // 每个小区间
f[i][j][k] += f[i + 1][j - 1][m];
f[i][j][k] %= MOD;
}
}
}
}
}
}
}
let mut res = 0;
for k in 0..4 { // 四个字符结果相加
res += f[0][n-1][k];
res %= MOD;
}
res
}
}
- 时间复杂度: O ( C × n 2 ) O(C\times n^2) O(C×n2)
- 空间复杂度: O ( n 2 ) O(n^2) O(n2)
总结
想到了应该动态规划,但是也不怎么下得了手,就cv过来然后学一下思路写其他语言。
这个用遍历顺序找最左和最右的点属实没有想到。
【出去浪了几天、开始还债、好在前几天都不是什么大难题……个p啊、线段树还没补】
欢迎指正与讨论! |