子序列——回文子序列个数(七)
2018-11-13 本文已影响0人
旺叔叔
LeetCode_730_CountDifferentPalindromicSubsequences
题目分析:
这是一个状态稍微复杂的题。dp的递推过程也是比较少见,我是第一次见。不过看懂后其实不难。
首先,我们不再是单纯地从左往右或者从右往左推,而是从两边往中间,
为什么呢,递归方式当然是跟随题目变化,也没人说不能酱紫呀。
定义dp[i][j]为字符串 i开头 j结尾 的子串的回文子序列个数。
观察左右两端的字符
1.不相等。S.charAt(i) != S.charAt(j)
dp[i][j] = dp[i][j - 1] + dp[i + 1][j] - dp[i + 1][j - 1];
就是等于 右边少一个 加上左边少一个 的情况,最后减去左右各少一个的情况,去重。
2.相等。S.charAt(i) == S.charAt(j)
稍微复杂一点了,根据ij中间是否有和S.charAt(i)相同的字符,要分三种情况
2.1没有两边相同的字字符
dp[i][j] = dp[i + 1][j - 1] * 2 + 2;
假设为b 无重复 那么等于中间个数 * 2 + 2
乘2是因为中间任意回文子序列都可以和首位加上b组成新的
+2是因为内部没有b 所以b和bb都是新的
2.2有且只有一个相同的字符
dp[i][j] = dp[i + 1][j - 1] * 2 + 1;
假设为b 重复1个 那么等于中间个数 * 2 + 1
乘2是因为中间任意回文子序列都可以和首位加上b组成新的
+1是因为内部有b 只有bb是新的
2.3有且有两个以上相同字符
dp[i][j] = dp[i + 1][j - 1] * 2 - dp[left + 1][right - 1];
假设为b 重复2个以上 那么等于中间个数 * 2 - 内部bb的首位组成的串的个数
乘2是因为中间任意回文子序列都可以和首位加上b组成新的
-内部bb的首位组成的串的个数 去重 (b和bb都被内部bb的首位组成的串算上了 也就不用+1 +2)
递推初始项
for (int i = 0; i < n; ++i) dp[i][i] = 1;
1.为何是对角线,注意ij的定义,ij只差越大,代表串越长,
我们的串,随着递推过程逐渐变短直到为1,这里对角线是最短字符串,
相当于递归的边界条件。
2.为何是1,也就是对角线,对角线开始和结束一样,也就是1个字符,当然是1.
b c c b
b 1 2 3 6
c 0 1 2 3
c 0 0 1 2
b 0 0 0 1
递推过程
从对角线,"逐斜行"往右上推,第一次见。
解法:
public static int countPalindromicSubsequences(String S) {
int n = S.length(), M = 1000000007;
int [][]dp = new int[n][n];
for (int i = 0; i < n; ++i) dp[i][i] = 1;
for (int len = 1; len < n; ++len) {
for (int i = 0; i < n - len; ++i) {
int j = i + len;
if (S.charAt(i) == S.charAt(j)) {
/**
* 这三句本质是为了求中间有多少个和左右两边相等的个数
* 使用双指针往中间移动到第一个等于两边数的情况
* left > right 表示没有重复 这种情况left能走到最右 right能走到最左
* left == right 表示只有一个重复 left right都在中间停下了
* left < right 表示至少2个重复
*/
int left = i + 1, right = j - 1;
while (left <= right && S.charAt(left) != S.charAt(i)) ++left;
while (left <= right && S.charAt(right) != S.charAt(i)) --right;
if (left > right) {
/**
* 假设为b 无重复 那么等于中间个数 * 2 + 2
* 乘2是因为中间任意回文子序列都可以和首位加上b组成新的
* +2是因为内部没有b 所以b和bb都是新的
*/
dp[i][j] = dp[i + 1][j - 1] * 2 + 2;
} else if (left == right) {
/**
* 假设为b 重复1个 那么等于中间个数 * 2 + 1
* 乘2是因为中间任意回文子序列都可以和首位加上b组成新的
* +1是因为内部有b 只有bb是新的
*/
dp[i][j] = dp[i + 1][j - 1] * 2 + 1;
} else {
/**
* 假设为b 重复2个以上 那么等于中间个数 * 2 - 内部bb的首位组成的串的个数
* 乘2是因为中间任意回文子序列都可以和首位加上b组成新的
* -内部bb的首位组成的串的个数 去重 (b和bb都被内部bb的首位组成的串算上了 也就不用+1 +2)
*/
dp[i][j] = dp[i + 1][j - 1] * 2 - dp[left + 1][right - 1];
}
} else {
/**
* 不相等 那么等于
* 右边少一个
* 加上左边少一个
* "减去"左右各少一个(最后这个减去 也是去重操作)
*/
dp[i][j] = dp[i][j - 1] + dp[i + 1][j] - dp[i + 1][j - 1];
}
/**
* 这是个漂亮的取模操作 将有的情况直接用+代替了%
*/
dp[i][j] = (dp[i][j] < 0) ? dp[i][j] + M : dp[i][j] % M;
}
}
return dp[0][n - 1];
}