• 统计字符串中不同回文子序列的个数


    统计字符串中不同回文子序列的个数

    作者:Grey

    原文地址: 统计字符串中不同回文子序列的个数

    问题描述

    给定一个字符串str,当然可以生成很多子序列,返回有多少个子序列是回文子序列,空序列不算回文,比如,str = “aba”回文子序列有

    {a}:0位置上的a
    {a}:2位置上的a
    {a,a}
    {b}
    {a,b,a}
    

    所以返回5。

    暴力解法

    枚举每个子序列,然后判断子序列是否是回文,代码如下

        public static int ways1(String str) {
            if (str == null || str.length() == 0) {
                return 0;
            }
            char[] s = str.toCharArray();
            char[] path = new char[s.length];
            return process(str.toCharArray(), 0, path, 0);
        }
        // 枚举每个位置要或者不要情况下,得到回文子序列的个数是多少
        public static int process(char[] s, int si, char[] path, int pi) {
            if (si == s.length) {
                return isP(path, pi) ? 1 : 0;
            }
            int ans = process(s, si + 1, path, pi);
            path[pi] = s[si];
            ans += process(s, si + 1, path, pi + 1);
            return ans;
        }
        // 判断path串中0...pi-1是不是回文
        public static boolean isP(char[] path, int pi) {
            if (pi == 0) {
                return false;
            }
            int L = 0;
            int R = pi - 1;
            while (L < R) {
                if (path[L++] != path[R--]) {
                    return false;
                }
            }
            return true;
        }
    

    动态规划解

    定义二维数组dp,数组长度假设为n

    int[][] dp = new int[n][n]
    

    dp[i][j]的含义是:字符串[i...j]区间内可以得到的最多回文子序列个数,所以,dp[0][n-1]的值就是我们需要求的最终答案。

    根据dp数组的含义,我们可以得到,二维矩阵dp中的对角线上的值都是1, 对角线上面的位置也可以很容易得出,见如下注释

    // 对角线上的值都是1
    for (int i = 0; i < n; i++) {
        dp[i][i] = 1;
    }
    // 对角线上面的位置,即dp[i][i+1]上的值
    // 如果str[i] == str[i+1],比如:aa,有三个回文子序列,分别是{a},{a},{aa}
    // 如果str[i] != str[i+1],比如:ab,有两个回文子序列,分别是 {a},{b}
    for (int i = 0; i < n - 1; i++) {
        dp[i][i+1] = str[i] == str[i+1] ? 3 : 2;
    }
    

    接下来考虑普遍位置dp[i][j],可以有如下几种情况:

    情况一,i位置的字符弃而不用,那么dp[i][j] = dp[i+1][j]

    情况二,j位置的字符弃而不用,那么dp[i][j] = dp[i][j-1]

    基于情况一和情况二,

    dp[i][j] = dp[i+1][j] + dp[i][j-1]
    

    这个时候,其实是算重了一部分,算重的部分是dp[i+1][j-1],所以

    dp[i][j] = dp[i+1][j] + dp[i][j-1] - dp[i+1][j-1]
    

    如果str[i] == str[j],则存在情况三,情况三中,str[i]str[j]可都保留,与区间[i+1...j-1]形成回文串,str[i]也可以单独和str[j]形成一个回文串。所以,情况三

    // dp[i][j]分成两部分
    // 其中:
    // 第一部分:dp[i + 1][j] + dp[i][j - 1] - dp[i + 1][j - 1] 表示i位置和j位置弃而不用的情况下,得到的答案数
    // 第二部分:dp[i + 1][j - 1]  + 1 表示在情况三下,同时使用str[i]和str[j]位置得到的答案数
    dp[i][j] = dp[i + 1][j] + dp[i][j - 1] - dp[i + 1][j - 1] +  dp[i + 1][j - 1]  + 1
    

    动态规划解法的完整代码

        public static int ways2(String s) {
            if (s == null || s.length() == 0) {
                return 0;
            }
            char[] str = s.toCharArray();
            int n = str.length;
            int[][] dp = new int[n][n];
            // 对角线都是1
            for (int i = 0; i < n; i++) {
                dp[i][i] = 1;
            }
            for (int i = 0; i < n - 1; i++) {
                dp[i][i + 1] = str[i] == str[i + 1] ? 3 : 2;
            }
            for (int i = n - 3; i >= 0; i--) {
                for (int j = i + 2; j < n; j++) {
                    // 减去dp[i+1][j-1]是因为算重复了
                    dp[i][j] = dp[i + 1][j] + dp[i][j - 1] - dp[i + 1][j - 1];
                    if (str[i] == str[j]) {
                        // dp[i][j]分成两部分
                        // 其中:
                        // 第一部分:dp[i + 1][j] + dp[i][j - 1] - dp[i + 1][j - 1] 表示i位置和j位置弃而不用的情况下,得到的答案数
                        // 第二部分:dp[i + 1][j - 1]  + 1 表示在情况三下,同时使用str[i]和str[j]位置得到的答案数
                        dp[i][j] = dp[i + 1][j] + dp[i][j - 1] - dp[i + 1][j - 1] +  dp[i + 1][j - 1]  + 1;
                    }
                }
            }
            return dp[0][n - 1];
        }
    

    类似问题

    LeetCode 730. 统计不同回文子序列

    更多

    算法和数据结构笔记

  • 相关阅读:
    最深叶节点的最近公共祖先
    ML-Agents(十)Crawler
    ML-Agents(九)Wall Jump
    ML-Agents(八)PushBlock
    ML-Agents(七)训练指令与训练配置文件
    Unity Editor扩展编辑器中显示脚本属性
    ML-Agents(六)Tennis
    数据结构(二)—栈
    ML-Agents(五)GridWorld
    ML-Agents(四)3DBall补充の引入泛化
  • 原文地址:https://www.cnblogs.com/greyzeng/p/16412505.html
Copyright © 2020-2023  润新知