栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

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

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

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

目录
  • 题目要求
  • 思路:动态规划
    • Java
    • C++
    • Rust
  • 总结

题目要求

思路:动态规划
  • 从小区间扩展至整个区间,所以区间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(Ctimes 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(Ctimes 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(Ctimes n^2) O(C×n2)
  • 空间复杂度: O ( n 2 ) O(n^2) O(n2)
总结

想到了应该动态规划,但是也不怎么下得了手,就cv过来然后学一下思路写其他语言。

这个用遍历顺序找最左和最右的点属实没有想到。

【出去浪了几天、开始还债、好在前几天都不是什么大难题……个p啊、线段树还没补】


欢迎指正与讨论!
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/960421.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号