#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!