Java&C++题解与拓展——leetcode730.统计不同回文子序列【区间DP】

每日一题做题记录,参考官方和三叶的题解

题目要求

在这里插入图片描述

思路:动态规划

  • 从小区间扩展至整个区间,所以区间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][n1]
  • 因为只有四个字母,所以枚举来统计,设当前枚举字符为 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啊、线段树还没补】


欢迎指正与讨论!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值