题目描述
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
题解
本题与leetcode647基本一致,看leetcode647即可
方法一 . 动态规划法
第 1 步:定义状态
dp[i][j] 表示子串 s[i…j] 是否为回文子串,这里子串 s[i…j] 定义为左闭右闭区间,可以取到 s[i] 和 s[j]。
第 2 步:思考状态转移方程
在这一步分类讨论(根据头尾字符是否相等):
dp[i][j] = (s[i] == s[j]) and dp[i + 1][j - 1]
看到 dp[i + 1][j - 1] 就得考虑边界情况。
这个结论很显然:j - i < 3 等价于 j - i + 1 < 4,即当子串 s[i…j] 的长度等于 2 或者等于 3 的时候,其实只需要判断一下头尾两个字符是否相等就可以直接下结论了。
如果子串 s[i + 1…j - 1] 只有 1 个字符,即去掉两头,剩下中间部分只有 1 个字符,显然是回文;
如果子串 s[i + 1…j - 1] 为空串,那么子串 s[i, j] 一定是回文子串。
因此,在 s[i] == s[j] 成立和 j - i < 3 的前提下,直接可以下结论,dp[i][j] = true,否则才执行状态转移。
第 3 步:考虑初始化
初始化的时候,单个字符一定是回文串,因此把对角线先初始化为 true,即 dp[i][i] = true 。
事实上,初始化的部分都可以省去。因为只有一个字符的时候一定是回文,dp[i][i] 根本不会被其它状态值所参考。
第 4 步:考虑输出
只要一得到 dp[i][j] = true,就记录子串的长度和起始位置,没有必要截取,这是因为截取字符串也要消耗性能,记录此时的回文子串的「起始位置」和「回文长度」即可。
代码实现
class Solution {
public String longestPalindrome(String s) {
//特判
int len = s.length();
if(len<2){
return s;
}
//最长回文子串的长度
int maxLen=1;
//最长回文子串的起始位置
int begin=0;
//dp[i][j]表示s[i,j]是否是回文串
boolean[][] dp = new boolean[len][len];
char[] charArray = s.toCharArray();
//初始化对角线上的所有值
for(int i=0;i<len;i++){
dp[i][i]=true;
}
//计算dp[i][j]数组的值
for(int j=1;j<len;j++){
for(int i=0;i<j;i++){
if(charArray[i] !=charArray[j]){
dp[i][j]=false;
}else{
//此时s[i,j]长度为2或3,又因为s[i]==s[j]
if(j-i<3){
dp[i][j]=true;
}else{
dp[i][j]=dp[i+1][j-1];
}
}
if(dp[i][j]&&j-i+1>maxLen){
maxLen = j-i+1;
begin=i;
}
}
}
return s.substring(begin,begin+maxLen);
}
}
注意:循环时要固定右边界而不是左边界(why?)
方法二:暴力解法
根据回文子串的定义,枚举所有长度大于等于 2 的子串,依次判断它们是否是回文;
可以只针对大于「当前得到的最长回文子串长度」的子串进行回文验证;
当得到了一个更长的回文时,不需要真的做截取。只需要记录「当前子串的起始位置」和「子串长度」。
public class Solution {
public String longestPalindrome(String s) {
int len = s.length();
if (len < 2) {
return s;
}
int maxLen = 1;
int begin = 0;
// s.charAt(i) 每次都会检查数组下标越界,因此先转换成字符数组
char[] charArray = s.toCharArray();
// 枚举所有长度大于 1 的子串 charArray[i..j]
for (int i = 0; i < len - 1; i++) {
for (int j = i + 1; j < len; j++) {
if (j - i + 1 > maxLen && validPalindromic(charArray, i, j)) {
maxLen = j - i + 1;
begin = i;
}
}
}
return s.substring(begin, begin + maxLen);
}
/**
* 验证子串 s[left..right] 是否为回文串
*/
private boolean validPalindromic(char[] charArray, int left, int right) {
while (left < right) {
if (charArray[left] != charArray[right]) {
return false;
}
left++;
right--;
}
return true;
}
}