一、题目描述
给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。
子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
示例 1:
输入:s = “bbbab”
输出:4
解释:一个可能的最长回文子序列为 “bbbb” 。
示例 2:
输入:s = “cbbd”
输出:2
解释:一个可能的最长回文子序列为 “bb” 。
提示:
1 <= s.length <= 1000
s 仅由小写英文字母组成
二、题目解析
1、动态规划
class Solution {
public int longestPalindromeSubseq (String s) {
// write code here
if(s == null || s.length() == 0){
return 0;
}
if(s.length() == 1){
return 1;
}
int n = s.length();
//dp[i][j]表示从i到j这段区间的字符串所构成的最长回文子序列长度
int[][] dp = new int[n][n];
//每个字符自身可构成一个回文
for(int i = 0;i < n;i++){
dp[i][i] = 1;
}
int max = 1;
//根据状态转移方程dp[i][j]和dp[i + 1][j - 1]有关,由此确定i和j的遍历顺序
for(int i = n - 1;i >= 0;i--){
//需要确保dp[i][j]是一个有效区间,故j从i+1开始计算即可
for(int j = i + 1;j < n;j++){
if(s.charAt(i) == s.charAt(j)){
//需要确保dp[i + 1][j - 1]是一个有效区间(下标为i+1到j-2区间的这段字符串)
//i = j - 1即i和j相邻
dp[i][j] = i <= j - 2 ? dp[i + 1][j - 1] + 2 : 2;
}else{
dp[i][j] = Math.max(dp[i + 1][j],dp[i][j - 1]);
}
max = dp[i][j] > max ? dp[i][j] : max;
}
}
//这里不需要max存储,直接return dp[0][n-1]即可
return max;
}
}
时间复杂度和空间复杂度都是O(n^2 )

思考:
若求最长回文子串,则dp不可定义为“从i到j这段区间的字符串所构成的最长回文子序列长度”,因为即使最长回文子序列长度存在,不一定满足子串(要求连续)。
所以dp应定义为“从i到j这段区间的字符串是否是最长回文子串”
public String longestPalindrome(String s) {
/*动态规划:
状态:dp[i][j]表示子串s[i,j]是否为回文子串
状态转移方程:dp[i][j]=dp[i+1][j-1] && s.charAt(i)==s.charAt(j)
时间复杂度O(n^2),空间复杂度O(n^2)
0 1 2 3 4 5
a b c b e f i逆序遍历
0 a t f f f f f ↑
1 b t f t(3) f f ↑
2 c t f f f ↑
3 b t f f ↑
4 e t f ↑
5 f t ↑
→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→ j正序遍历
* */
if(s == null || s.length() == 0){
return null;
}
int n = s.length();
boolean[][] dp = new boolean[n][n];
//二维数组对角线上的值是true即单个字符默认是回文
for (int i = 0; i < n; i++) {
dp[i][i] = true;
}
//初始化最长回文子串长度是1,此时的回文起点是0
int maxLength = 1;
int start = 0;
//由状态转移方程可知i需反序遍历,j需正序遍历
//由于遍历过程中保持i<=j,又=j时为true,所以只需要考虑二维表格上半部分即可
for (int i = n - 1; i >= 0; i--) {
for (int j = i + 1; j < n; j++) {
if(s.charAt(i) == s.charAt(j)){
//考虑子串的有效性j-i>=2,此时区间[i+1][j-1]存在
if(i + 1 <= j - 1){
dp[i][j] = dp[i + 1][j - 1];
}else{
//此时j-i<2,即两指针正好紧挨着,又这两个元素相等,所以必定回文
dp[i][j] = true;
}
}
//如果s[i,j]区间是回文且长度更大则更新maxLength和start
if(dp[i][j] == true && j - i + 1 > maxLength){
maxLength = j - i + 1;
start = i;
}
}
}
return s.substring(start,start + maxLength);
}
类似于最长回文子序列的dp定义,
dp也可定义为子串[i,j](注意:i到j严格连续)
为最长回文子串时的长度,(不可为子串[i,j]中最长回文子串的长度,因为这样无法由dp[i+1][j-1]推导出dp[i][j],accaba为例,首尾a相等,但accaba最长回文子串是accaba而不是accaba)
dp可定义为子串[i,j](注意:i到j严格连续)
为最长回文子串时的长度时
这里容易犯错,判断s.charAt(i)==s.charAt(j),就赋值dp[i][j] = dp[i+1][j-1] + 2,这里并不严谨。
以“accaba为例”,首尾a等于a,但是ccab不是回文,accaba自然也不是回文,若按照上述公式会出现错判
正确的状态转移方程:
- s.charAt(i)==s.charAt(j)
- dp[i+1][j-1] > 0 (子串【i+1,j-1】必须为回文子串)-
i + 1 >= j - 1
- dp[i][j] = dp[i+1][j-1] + 2
-
i + 1 < j - 1
- dp[i][j] = 2
-
- s.charAt(i)!=s.charAt(j)
- dp[i][j] = 0(0表示不是回文)
在最长回文子序列代码的基础上改造:
- dp[i][j] = 0(0表示不是回文)
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param A string字符串
* @return int整型
*/
public int getLongestPalindrome (String s) {
// write code here
if(s == null || s.length() == 0){
return 0;
}
if(s.length() == 1){
return 1;
}
int n = s.length();
//dp[i][j]表示从i到j这段区间的字符串所构成的最长回文子序列
int[][] dp = new int[n][n];
//每个字符自身可构成一个回文
for(int i = 0;i < n;i++){
dp[i][i] = 1;
}
int max = 1;
//根据状态转移方程确定i和j的遍历顺序
for(int i = n - 1;i >= 0;i--){
//需要确保dp[i][j]是一个有效区间,故j从i+1开始计算即可
for(int j = i + 1;j < n;j++){
if(s.charAt(i) == s.charAt(j) ){
//需要确保dp[i + 1][j - 1]是一个有效区间(下标为i+1到j-2区间的这段字符串)
if(i + 1 <= j - 1 && dp[i + 1][j - 1] > 0){
dp[i][j] = dp[i + 1][j - 1] + 2;
}else if(i + 1 > j - 1){ **//特殊情况:表示两个元素相邻**
dp[i][j] = 2;
}
}else{
dp[i][j] = 0;
}
max = dp[i][j] > max ? dp[i][j] : max;
}
}
//这里不需要max存储,直接return dp[0][n-1]即可
return max;
}
}
548

被折叠的 条评论
为什么被折叠?



