#Java #动态规划
Feeling and experiences:
回文子串:力扣题目链接
给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。
回文字符串 是正着读和倒过来读一样的字符串。
子字符串 是字符串中的由连续字符组成的一个序列。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
示例 1:
输入:s = "abc" 输出:3 解释:三个回文子串: "a", "b", "c"
动态规划在 回文子串上的运用
之前做回文子串的问题,一般会用到 双指针 和 回溯
class Solution {public int countSubstrings(String s) {int n = s.length();  // 字符串的长度int ans = 0;  // 记录回文子串的个数for (int i = 0; i < 2 * n - 1; ++i) {int l = i / 2, r = i / 2 + i % 2;  // 根据奇偶性确定回文中心的左右边界while (l >= 0 && r < n && s.charAt(l) == s.charAt(r)) {--l;  // 向左扩展++r;  // 向右扩展++ans;  // 找到一个新的回文子串}}return ans;  // 返回最终的回文子串个数}
}
双指针的 思路很简单:
就是遍历每个可能的中心点,然后我们从 中心点 用两个指针开始遍历元素,判断是否为回文
至于 中心点 为什么是 2*n - 1 个:
(来自力扣评论中的一条解释):
为什么是2n-1个中心点?
如果回文串是奇数,我们把回文串中心的那个字符叫做中心点,如果回文串是偶数我们就把中间的那两个字符叫做中心点。
对于一个长度为n的字符串,我们可以用它的任意一个字符当做中心点,所以中心点的个数是n。我们还可以用它任意挨着的两个字符当做中心点,所以中心点是n-1,总的中心点就是2*n-1。
动态规划的做法:
class Solution {public int countSubstrings(String s) {int len = s.length();boolean[][] dp = new boolean[len][len];  // dp[i][j] 表示从索引 i 到 j 的子串是否为回文int result = 0;  // 记录回文子串的个数for (int i = len - 1; i >= 0; i--) {for (int j = i; j < len; j++) {if (s.charAt(i) == s.charAt(j)) {if (j - i <= 1) {result++;  // 单个字符或相邻字符相同,是回文子串dp[i][j] = true;} else if (dp[i + 1][j - 1]) { result++;  // 利用动态规划,如果内部子串是回文,则整个子串也是回文dp[i][j] = true;}}}}return result;}
}
-  使用动态规划数组 dp,其中dp[i][j]表示从索引i到j的子串是否为回文。
-  从字符串的最后一个字符开始,逆序遍历到第一个字符。这样可以确保在计算 dp[i][j]时,dp[i+1][j-1]已经被计算过,以便利用动态规划的结果。
-  在每个位置 (i, j),检查字符s.charAt(i)和s.charAt(j)是否相同,如果相同,判断子串是否为回文:- 如果 j - i小于等于 1,说明子串长度为 1 或 2,一定是回文,此时增加回文子串的计数。
- 如果 j - i大于 1,利用动态规划的结果,如果内部子串是回文,则整个子串也是回文,同样增加回文子串的计数。
 
- 如果 
最长回文子序列:力扣题目链接
给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。
子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
示例 1:
输入:s = "bbbab" 输出:4 解释:一个可能的最长回文子序列为 "bbbb" 。
该题和 子串问题差不多
class Solution {public int longestPalindromeSubseq(String s) {// 本题是子序列问题,和子串问题类似// 创建dp数组,定义:区间[i,j] 左闭右闭 最大的回文子序列为dp[i][j]int[][] dp = new int[s.length()][s.length()];// 初始化为 java 默认为0// 循环,遍历for (int i = s.length() - 1; i >= 0; i--) {for (int j = i; j < s.length(); j++) {if (s.charAt(i) == s.charAt(j)) {if (j - i <= 1) {dp[i][j] = j - i + 1;} else {dp[i][j] = dp[i + 1][j - 1] + 2;}} else {dp[i][j] = Math.max(dp[i + 1][j], Math.max(dp[i][j - 1], dp[i][j]));}}}return dp[0][s.length() - 1];}
}
-  从字符串的末尾开始循环,外层循环变量为 i,内层循环变量为j。这样可以确保在计算dp[i][j]时,dp[i+1][j-1]已经被计算过,以便利用动态规划的结果。
-  对于每一对索引 (i, j),如果s[i]和s[j]相等,那么:- 如果 j - i小于等于 1,说明子串长度为 1 或 2,直接赋值dp[i][j] = j - i + 1。
- 否则,利用动态规划的结果,dp[i][j] = dp[i + 1][j - 1] + 2。
 
- 如果 
-  如果 s[i]和s[j]不相等,那么dp[i][j]取左边、上边和左上角三个位置的最大值,即dp[i][j] = Math.max(dp[i + 1][j], Math.max(dp[i][j - 1], dp[i][j]))。
风不定,人初静,明日落红应满径。
Fighting!