647. 回文子串
题目描述
给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。
回文字符串 是正着读和倒过来读一样的字符串。
子字符串 是字符串中的由连续字符组成的一个序列。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
提示:
- 1 <= s.length <= 1000
- s 由小写英文字母组成
暴力
分别枚举 start、end、k,判断 每个子串是否为 回文串。
- 时间复杂度: O ( n 3 ) O(n^3) O(n3)
dp 五步曲
-
dp[i][j]
表示 s[i, j] (即,j >= i)是否为回文串(true:是回文串;false:不是) -
递推公式
仍然分为 2 种情况,即 s[i]和s[j]相等、s[i]和s[j]不相等:- s[i]和s[j] 不相等时,则 s[i, j] 一定不是 回文串,dp[i][j] = false;
- s[i]和s[j] 相等时,分为 3 种情况:
- 1)i == j,即
只有一个字符时
,一定是回文串; - 2)i + 1 == j,即
有2个字符时
,也是回文串; - 3)i + 1 < j,即
至少有3个字符时
,则需要看 s[i+1, j-1] 是不是 回文串
- 1)i == j,即
// 2、递推公式 if (s.charAt(i) == s.charAt(j)) { // 当前字符相等,分为 3 种情况: // (1)i == j,即 只有一个字符时,一定是回文串 // (2)i + 1 == j,即 有2个字符时,也是回文串 // (3)i + 1 < j,即 至少有3个字符时,则需要看 s[i+1, j-1] 是不是 回文串 if (i == j || i + 1 == j) { // (1)、(2) dp[i][j] = true; } else { // (3)多于2个字符 dp[i][j] = dp[i + 1][j - 1]; } } else { // 当前字符 不相等,则 s[i, j] 一定不是 回文串 dp[i][j] = false; }
-
初始化(dp[i][j] 全部为 false)
-
遍历顺序
由递推公式可知,当前状态依赖其 左下方,所以需要 从下到上、从左到右 -
打印dp
class Solution {
public int countSubstrings(String s) {
int n = s.length();
boolean[][] dp = new boolean [n][n]; // dp[i][j] 表示 s[i, j] (即,j >= i)是否为回文串(true:是回文串;false:不是)
// 3、初始化(dp[i][j] 全部为 false)
// 4、顺序(由递推公式可知,当前状态依赖其 左下方,所以需要 从下到上、从左到右)
int res = 0;
for (int i = n - 1; i >= 0; i--) { // 从下到上
for (int j = i; j < n; j++) { // 从左到右
// 2、递推公式
if (s.charAt(i) == s.charAt(j)) {
// 当前字符相等,分为 3 种情况:
// (1)i == j,即 只有一个字符时,一定是回文串
// (2)i + 1 == j,即 有2个字符时,也是回文串
// (3)i + 1 < j,即 至少有3个字符时,则需要看 s[i+1, j-1] 是不是 回文串
if (i == j || i + 1 == j) { // (1)、(2)
dp[i][j] = true;
} else { // (3)多于2个字符
dp[i][j] = dp[i + 1][j - 1];
}
} else { // 当前字符 不相等,则 s[i, j] 一定不是 回文串
dp[i][j] = false;
}
if (dp[i][j]) res++;
}
}
// 5、打印dp
for (int i = 0; i < n; i++) {
System.out.println(Arrays.toString(dp[i]));
}
return res;
}
}
这里维护的是 dp 上三角
5. 最长回文子串
题目描述
给你一个字符串 s,找到 s 中最长的回文子串。
提示:
- 1 <= s.length <= 1000
- s 仅由数字和英文字母(大写和/或小写)组成
和 647. 回文子串 基本类似,仍然是 回文子串(连续) 问题。
本题需要在 647. 回文子串 基础上 维护一个 maxLen、left、right(最长回文串的起始、终止位置),找到 最长的回文子串。
class Solution {
public String longestPalindrome(String s) {
int left = 0;
int right = 0;
int maxLen = 0;
int n = s.length();
// 1、dp[i][j] 表示 [i, j] 子串是否为 回文子串
boolean[][] dp = new boolean[n][n];
// 3、初始化(将 dp[i][j] 都置为 false 即可)
// 4、遍历顺序(根据递推公式可知:从下向上,从左向右)
for (int i = n - 1; i >= 0; i--) {
for (int j = 0; j < n; j++) {
// 2、递推公式
if (s.charAt(i) == s.charAt(j)) {
if (j - i <= 1) { // 一个字符 或 2个字符且相等,都是 回文子串
dp[i][j] = true;
} else if (dp[i + 1][j - 1]) { // 当 len >=3 时,需要看 [i+1, j-1] 是否为 回文子串
dp[i][j] = true;
}
}
if (dp[i][j] && (j - i + 1) > maxLen) { // !!!差别
maxLen = j - i + 1;
left = i;
right = j;
}
}
}
System.out.println("left = " + left);
System.out.println("right = " + right);
return s.substring(left, right + 1); // !!!差别
}
}
516. 最长回文子序列
题目描述
给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。
子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
本题求的是 最长回文子序列,注意和 5. 最长回文子串 区别:
- 子学历:可以不连续
- 子串:必须连续
dp
dp 五步曲:
dp[i][j]
表示 s[i, j] (闭区间) 中最长回文子序列(不一定连续)的长度- 递推公式
仍然分 2 大类情况,即 s[i]和s[j]相等、s[i]和s[j]不相等:- s[i]和s[j] 相等,只用看 s[i+1, j-1] 的最长回文子序列 + 2 即可,即 dp[i][j] = dp[i + 1][j - 1] + 2;
- s[i]和s[j] 不相等,分 2 小种情况:
- 1)
删除 s[i]
,则看 s[i+1, j] 是否为 回文串,即 dp[i + 1][j]; - 2)
删除 s[j]
,则看 s[i, j-1] 是否为 回文串,即 dp[i][j - 1] - 二者,取 max
- 1)
- 初始化
dp[i][0]、dp[0][j] 均为 0;
但是 dp[i][i] 在递推公式中 处理不到,所以 要初始化dp[i][i] = 1
- 顺序
由递推公式可知,当前状态依赖 下方、左边,所以 从下到上、从左到右 - 打印dp
class Solution {
public int longestPalindromeSubseq(String s) {
int n = s.length();
int[][] dp = new int[n][n]; // dp[i][j] 表示 s[i, j] (闭区间) 中最长回文子序列(不一定连续)的长度
// 3、初始化(dp[i][0]、dp[0][j] 均为 0;但是 dp[i][i] 在递推公式中 处理不到,所以 要初始化 dp[i][i] = 1)
for (int i = 0; i < n; i++) dp[i][i] = 1;
// 4、顺序(由递推公式可知,当前状态依赖 下方、左边,所以 从下到上、从左到右)
for (int i = n - 1; i >= 0; i--) { // 从下到上
for (int j = i + 1; j < n; j++) { // 从左到右(这里 j 必须从 i+1 开始,而不是从 i 开始)
// 2、递推公式
if (s.charAt(i) != s.charAt(j)) {
// 当前字符 不相等,分 2 中情况:
// (1)删除 s[i],则看 s[i+1, j] 是否为 回文串
// (2)删除 s[j],则看 s[i, j-1] 是否为 回文串
// 二者,取 max
dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]);
} else { // 当前字符 相等
dp[i][j] = dp[i + 1][j - 1] + 2;
}
}
}
// 5、打印dp
// for (int i = 0; i < n; i++) {
// System.out.println(Arrays.toString(dp[i]));
// }
return dp[0][n - 1];
}
}