对于一个字符串, 从前开始读和从后开始读是一样的, 我们就称这个字符串是回文串。例如"ABCBA","AA", "A" 是回文串, 而"ABCD", "AAB"不是回文串。牛牛特别喜欢回文串, 他手中有一个字符串s, 牛牛在思考能否从字 符串中移除部分(0个或多个)字符使其变为回文串,并且牛牛认为空串不是回文串。牛牛发现移除的方案可能有很多种, 希望你来帮他计算一下一共有多少种移除方案可以使s变为回文串。对于两种移除方案, 如果移除的字符依次构成的序列不一样就是不同的方案。
例如,XXY 4种 ABA 5种
【说明】 这是今年的原题,提供的说明和例子都很让人费解。现在根据当时题目的所有测试用例,重新解释当时的题目 含义:
1)"1AB23CD21",你可以选择删除A、B、C、D,然后剩下子序列{1,2,3,2,1},只要剩下的子序列是同一个,那 么就只算1种方法,和A、B、C、D选择什么样的删除顺序没有关系。
2)"121A1",其中有两个{1,2,1}的子序列,第一个{1,2,1}是由{位置0,位置1,位置2}构成,第二个{1,2,1} 是由{位置0,位置1,位置4}构成。这两个子序列被认为是不同的子序列。也就是说在本题中,认为字面值一样 但是位置不同的字符就是不同的。
3)其实这道题是想求,str中有多少个不同的子序列,每一种子序列只对应一种删除方法,那就是把多余的东西去掉,而和去掉的顺序无关。
4)也许你觉得我的解释很荒谬,但真的是这样,不然解释不了为什么,XXY 4种 ABA 5种,而且其他的测试用例都印证了这一点。
本题是范围上的尝试模型。
使用动态规划的思想进行解题。定义二维数组dp[i][j],行列分别用字符串str的字符表示,二维数组dp[i][j]的含义是:字符串从i开始到j结尾子序列是回文串有几种方案数。
二维数的左下部分不需要填写。
二维数组对角线dp[i][i]全部填一。紧挨着对角线的右上对角线dp[i][i+1]表示字符串从第i个字符到第j=i+1个字符是回文串有几种方案数,我们很容易知道如果str[i]==str[i+1]那么有三种可能方案,否则为两种可能方案。
对于二维数组的其他位置dp[L][R],有如下几种可能性:
这四种情况是相互互斥的,dp[L][R] = a + b + c + d (情况4成立) 或者 dp[i][j] = a + b + c (情况4不成立)。
dp[L+1][R]表示L位置不在最后的回文字符串中的方法数,其结果为a + c。
dp[L][R-1]表示R位置不在最后的回文字符串中的方法数,其结果为a + b。
dp[L+1][R-1]表示L和R位置都不在最后的回文字符串中的方法数,其结果为a。
综合上述: a+ b + c = dp[L+1][R] + dp[L][R-1] - dp[L+1][R-1] 。
现在就剩下d的值未算出,有d可能新结果说明str[L]==str[R]条件成立。字符串中两端的字符固定中间字符串是回文串有几种方案数为a,此时我们需要格外注意还有一种可能性就是中间字符串字符全部删除,所以d = a + 1 。
- public static int way(String str) {
- char[] s = str.toCharArray();
- int n = s.length;
- int[][] dp = new int[n][n];
- for (int i = 0; i < n; i++) {
- dp[i][i] = 1;
- }
- for (int i = 0; i < n - 1; i++) {
- dp[i][i + 1] = s[i] == s[i + 1] ? 3 : 2;
- }
- for (int L = n - 3; L >= 0; L--) {
- for (int R = L + 2; R < n; R++) {
- dp[L][R] = dp[L + 1][R] + dp[L][R - 1] - dp[L + 1][R - 1];
- if (s[L] == s[R]) {
- dp[L][R] += dp[L + 1][R - 1] + 1;
- }
- }
- }
- return dp[0][n - 1];
- }
-
- public static void main(String[] args) {
- System.out.println(way("ABA"));
- System.out.println(way("XXY"));
- }