
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 1:
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
示例 2:
输入: "cbbd"
输出: "bb"
方法一:暴力匹配法
- 枚举所有长度大于2的子串,分别判断是否为回文子串。
代码如下
public String longestPalindrome(String s) {
int len = s.length();
if(len < 2){
return s;
}
int start = 0,maxLen = 1;
char[] charArray = s.toCharArray();
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)){
start = i;
maxLen = j - i + 1;
}
}
}
return s.substring(start,start + maxLen);
}
private boolean validPalindromic(char[] charArray, int left, int right) {
while (left < right) {
if (charArray[left] != charArray[right]) {
return false;
}
left++;
right--;
}
return true;
}
- 若使用字符串的charAt(i)方法,每次都会检查数组下标越界,转化为字符数组后可提高性能。
- 时间复杂度:O(N³),枚举字符串的左边界、右边界,然后继续验证子串是否是回文子串,这三种操作都与 N 相关。
- 空间复杂度:O(1),只使用到常数个临时变量,与字符串长度无关。
- 暴力解法时间复杂度高,但是思路清晰、编写简单。由于编写正确性的可能性很大,可以使用暴力匹配算法检验我们编写的其它算法是否正确。优化的解法在很多时候,是基于“暴力解法”,以空间换时间得到的,因此思考清楚暴力解法,分析其缺点,很多时候能为我们打开思路。
方法二:动态规划
- 动态规划的一般步骤:定义状态、定义状态转移方程(注意边界条件)、设置初始化、考虑输出、考虑优化空间(表格复用)。
- 对于本题我们用
dp[i][j]子串s[i]到子串s[j]是否为回文子串。
- 本题中判断是否为回文子串的条件为
dp[i][j] = (s[i] == s[j]) and dp[i + 1][j - 1]),所以我们一定要先算出dp[i + 1][j - 1]的状态,然后再算dp[i][j],这样dp[i][j]才能参考dp[i+1][j-1]的状态.
- 如表格所示,所以每一个dp[i][j]都要参考位于它左下角的值,这里我们采用先升序填列,再升序填行的方式,保证需要参考时,左下角一定有值。所以填的顺序为
[1][0],[2][0],[2][1],[3][0]...,以此类推。

- 边界条件即
[i + 1, j - 1] 不构成区间,所以我们得出,当[i,j]长度为1时,该子串为回文子串,当[i,j]长度为2或者3时,若 s[i] == s[j],该子串为回文子串,若条件不成立,即j-i<3时,则执行状态转移。
- 单个字符一定是回文串,因此把对角线先初始化为 true,即
dp[i][i] = true 。
- 本题中我们只需要输出最长回文子串,因此我们只需要记录开始位置和子串长度即可,这相比于每次截取字符串而言,性能更高。
代码如下
public String longestPalindrome(String s) {
int len = s.length();
if (len < 2) {
return s;
}
int maxLen = 1;
int begin = 0;
boolean[][] dp = new boolean[len][len];
char[] charArray = s.toCharArray();
for (int i = 0; i < len; i++) {
dp[i][i] = true;
}
for (int j = 1; j < len; j++) {
for (int i = 0; i < j; i++) {
if (charArray[i] != charArray[j]) {
dp[i][j] = false;
} else {
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);
}
方法三:中心扩散
- 在暴力解法和动态规划中,我们都是采用的枚举左右边界的形式,这样时间复杂度一定会有一个O(n²),那我们能不能换一种思路,来枚举所有可能的中心点。
- 在法三中我们通过枚举可能出现的回文子串的“中心位置”,从“中心位置”尝试尽可能扩散出去,得到一个回文串。
- 当子串长度为奇数时,中心位置为单个字符,当子串长度为偶数时,我们可以把中心位置看成两个字符串,若两个字符串相等,则向两边遍历。
代码如下
public String longestPalindrome1(String s) {
int len = s.length();
if (len < 2) {
return s;
}
int maxLen = 1;
String res = s.substring(0, 1);
for (int i = 1; i < len - 1; i++) {
String oddStr = centerSpread(s, i, i);
String evenStr = centerSpread(s, i, i + 1);
String maxLenStr = oddStr.length() > evenStr.length() ? oddStr : evenStr;
if (maxLenStr.length() > res.length()) {
res = maxLenStr;
}
}
return res;
}
private String centerSpread(String s, int left, int right) {
int len = s.length();
int i = left;
int j = right;
while (i > 0 && j < len) {
if (s.charAt(i) == s.charAt(j)) {
i--;
j++;
} else {
break;
}
}
return s.substring(i + 1, j);
}