①、回文子串
给你一个字符串
s
,请你统计并返回这个字符串中 回文子串 的数目。回文字符串 是正着读和倒过来读一样的字符串。
子字符串 是字符串中的由连续字符组成的一个序列。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
事例:
输入:s = "abc" 输出:3 解释:三个回文子串: "a", "b", "c"
思路:
使用动态规划,dp为二维数组,以第一二维切割字符串。如dp[i][j]表示字符串s下标从i到j是否为回文字串,统计所有dp[i][j]为true的个数。
动态规划:
dp定义及含义:dp[i][j]表示字符串s下标从i到j是否为回文字串
状态转移方程:如果s[i] == s[j]的话,若中间为回文字串,则dp[i][j] = true,考虑到j一定大于i,故分为两种情况,若j - i <= 1或者 dp[i + 1][j - 1],则dp[i][j] = true。
以dp视图来看,dp依赖于左下角赋值,故遍历顺序需先计算左下角。
遍历顺序:先从大到小遍历s,再从小到大遍历,j依赖于i
最终输出统计个数res
代码:
public int countSubstrings(String s) {
int res = 0;
boolean[][] dp = new boolean[s.length()][s.length()];
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){
res++;
dp[i][j] = true;
}else{
if(dp[i + 1][j - 1]){
dp[i][j] = true;
res++;
}
}
}
}
}
return res;
}
②、最长回文子序列
给你一个字符串
s
,找出其中最长的回文子序列,并返回该序列的长度。子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
事例:
输入:s = "bbbab" 输出:4 解释:一个可能的最长回文子序列为 "bbbb" 。
思路:
切割跟上题类似,使用动态规划。dp[i][j]表示字符串s下标从i到j的最长回文子序列长度。若s[i] == s[j],则说明回文长度可以加上2,dp[i][j] = dp[i + 1][j - 1] + 2,若不能同时使用这两字符,则dp[i][j] = Math.max(dp[i + 1][j],dp[i][j - 1])。
动态规划:
dp定义及含义:dp[i][j]表示字符串s下标从i到j的最长回文子序列长度
状态转移方程:if(s[i] == s[j]) dp[i][j] = dp[i + 1][j - 1] + 2
else dp[i][j] = Math.max(dp[i + 1][j],dp[i][j - 1])
初始化:dp[i][i] = 1,因为这样切割只有一个字符,算回文字符串
遍历顺序:与上题一样,先从大到小遍历,再嵌套从小到大遍历
dp[0][s.length() - 1]即为答案。
代码:
public int longestPalindromeSubseq(String s) {
int[][] dp = new int[s.length()][s.length()];
for(int i = 0;i < s.length();i++){
dp[i][i] = 1;
}
for(int i = s.length() - 1;i >= 0;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][j] = Math.max(dp[i + 1][j],dp[i][j - 1]);
}
}
}
return dp[0][s.length() - 1];
}