回文子串
题目:给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。
回文字符串 是正着读和倒过来读一样的字符串。
子字符串 是字符串中的由连续字符组成的一个序列。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
- dp[i][j] 表示[i,j]范围内的字符串是否是回文字符串
- 递推公式:判断s.charAt(i) 和 s.charAt(j)是否相等
- 相等:如果i==j,说明只有一个字符,肯定是回文,例如
a
;如果j-1 = i,说明只有两个字符,并且相等,也肯定是回文,例如aa
;如果j-1>i,这时候就需要判断i+1到j-1的范围是否是回文了
- 不相等:那肯定就不是了
- 相等:如果i==j,说明只有一个字符,肯定是回文,例如
- dp数组初始化:默认全是false
- 遍历顺序:我们的dp[i][j]主要是由于dp[i+1][j-1]推导出来的,所有我们应该先从大到小遍历i,然后从小到大遍历j
- 打印dp数组
class Solution {
public int countSubstrings(String s) {
// dp[i][j] 表示[i,j]范围内的字符串是否是回文字符串
boolean[][] dp = new boolean[s.length()][s.length()];
// 初始化 dp[i][j] = false;
// 记录结果
int res = 0;
for(int i = s.length()-1;i>=0;i--){
for(int j = i;j<s.length();j++){// 根据dp定义知道j是大于i的所以这里j至少需要从i开始
if(s.charAt(i) == s.charAt(j)){
if(j-i<=1){
dp[i][j] = true;
res++;
}else{
if(dp[i+1][j-1]){
dp[i][j] = true;
res++;
}
}
}
}
}
return res;
}
}
最长回文子序列
题目:给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。
子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
思路:本题和上一题的最大的区别在于,上一题求的是子字符串(必须连续),本题是子序列(可以不连续)
- dp[i][j]表示[i,j]范围内的字符串中最长回文子序列的最大长度为dp[i][j]
- 递推公式:s.charAt(i) 和 s.charAt(j)是否相等
- 相等:dp[i][j] = dp[i+1][j-1] + 2;因为是左右两个字符,所有加2
- 不相等:dp[i][j] = Math.max(dp[i+1][j],dp[i][j-1]);
- 保留j位置字符:dp[i+1][j]
- 保留i位置字符:dp[i][j-1]
- dp初始化: dp[i][i] = 1,因为当i=j的时候就是只有一个字符,一个字符也是回文子序列,长度为1
- 遍历顺序:本题和上题一样
- 打印dp
class Solution {
public int longestPalindromeSubseq(String s) {
// dp[i][j]表示[i,j]范围内的字符串中最长回文子序列的最大长度为dp[i][j]
int[][] dp = new int[s.length()][s.length()];
// 初始化 dp[i][i] = 1
for(int i = 0;i<s.length();i++){
dp[i][i] = 1;
}
for(int i = s.length()-1;i>=0;i--){
// 为什么j从i+1开始,因为j=i这种情况我们已经在初始化的时候处理过了
for(int j = i+1;j<s.length();j++){
if(s.charAt(i) == s.charAt(j)){
dp[i][j] = dp[i+1][j-1] + 2;
}else{
// dp[i+1][j] 保留j位置的字符
// dp[i][j-1] 保留i位置的字符
dp[i][j] = Math.max(dp[i+1][j],dp[i][j-1]);
}
}
}
return dp[0][s.length()-1];
}
}