3

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

 2 years ago
source link: https://www.cnblogs.com/greyzeng/p/16412505.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

作者: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. 统计不同回文子序列

更多#

算法和数据结构笔记


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK