题目描述
给你一个字符串 s,找到 s 中最长的回文子串。
示例
示例 1:
输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。
示例 2:
输入:s = “cbbd”
输出:“bb”
解题过程
思路及步骤
(1)暴力解法:双层循环去判断每个子串是否为回文串,如果是,则比较每个回文串的长度,取最长的,最后从原始字符串中截取出即可;
(2)中心扩散法:以当前元素或者当前元素的下一个或当前元素的下几个元素作为中心点,分别向左右两边扩散,判断扩散之后的子串是不是回文串,若是,则根据实际情况更新最长回文串的长度(若当前元素与下一个元素不相等,那么就以当前元素作为中心点去扩散;若当前元素只和下一个元素相等,那么就以当前元素和下一个元素作为中心点去扩散;若当前元素与之后的若干个元素都相等,那么就以当前元素和其之后的若干个元素作为中心点去扩散);
(3)动态规划法:有点类似于暴力解法,使用二维数组 dp[][] 去记录 dp[left][right] 位置对应的元素是否是回文,如果是回文串,则更新 dp[left][right] = true,如果不是,则更新 dp[left][right] = false;
(4)马拉车法:后续解释
代码展示
public class LongestPalindrome {
/**
* 暴力求解
* @param s
* @return
*/
public static String longestPalindrome1(String s) {
if (s.length() <= 1) {
return s;
}
// 记录最长回文串的长度
int maxLength = 0;
// 记录最长回文串的开始下标
int startIndex = 0;
for (int i = 0; i < s.length(); i++) {
for (int j = i; j < s.length(); j++) {
// 当前子串的长度
int currentLength = j - i + 1;
// 如果当前区间的长度比最大回文串长度小, 则直接 continue
if (currentLength <= maxLength) {
continue;
}
// 判断当前 [i, j] 区间内的子串是不是回文
if (isPalindrome(s, i, j)) {
// 可将回文串输出
// System.out.println(s.substring(i, j + 1));
// 如果为回文子串且 则更新 maxLength
startIndex = i;
maxLength = currentLength;
}
}
}
return s.substring(startIndex, startIndex + maxLength);
}
private static boolean isPalindrome(String s, int i, int j) {
while (i < j) {
if (s.charAt(i) != s.charAt(j)) {
return false;
}
i++;
j--;
}
return true;
}
/**
* 中心扩散法
* @param s
* @return
*/
public static String longestPalindrome2(String s) {
if (s.length() <= 1) {
return s;
}
// 记录最长回文串的长度
int maxLength = 0;
// 记录最长回文串的开始下标
int startIndex = 0;
for (int i = 0; i < s.length();) {
int left = i;
int right = i;
// 如果当前字符后面的几个字符都是相同的, 那么看做一个整体进行扩散
while (right < s.length() - 1 && s.charAt(i) == s.charAt(right + 1)) {
right++;
}
// 更新循环变量 i, 下次循环直接从 i = right + 1 开始
i = right + 1;
// 向左右扩散, 寻找最长回文串
while (left > 0 && right < s.length() - 1 && s.charAt(right + 1) == s.charAt(left - 1)) {
right++;
left--;
}
// 当前子串的长度
int currentLength = right -left + 1;
if (currentLength > maxLength) {
maxLength = currentLength;
startIndex = left;
}
}
return s.substring(startIndex, startIndex + maxLength);
}
/**
* 动态规划法
* @param s
* @return
*/
public static String longestPalindrome3(String s) {
if (s.length() <= 1) {
return s;
}
// 记录最长回文串的长度
int maxLength = 0;
// 记录最长回文串的开始下标
int startIndex = 0;
boolean[][] dp = new boolean[s.length()][s.length()];
for (int right = 0; right < s.length(); right++) {
for (int left = 0; left <= right; left++) {
if (s.charAt(left) != s.charAt(right)) {
dp[left][right] = false;
continue;
} else {
if (right - left <= 2) {
dp[left][right] = true;
} else {
dp[left][right] = dp[left + 1][right - 1];
}
}
if (dp[left][right]) {
if (right - left + 1 > maxLength) {
maxLength = right - left + 1;
startIndex = left;
}
}
}
}
// for (int i = 0; i < dp.length; i++) {
// for (int j = 0; j < dp[i].length; j++) {
// int number = 0;
// if (dp[i][j]) {
// number = 1;
// }
// System.out.printf("%2d", number);
// }
// System.out.println();
// }
return s.substring(startIndex, startIndex + maxLength);
}
public static void main(String[] args) {
String s1 = "babad";
String palindrome = longestPalindrome1(s1);
System.out.println(palindrome);
String s2 = "bcabbbacd";
String palindrome2 = longestPalindrome2(s2);
System.out.println(palindrome2);
String s3 = "acbeaebc";
String palindrome3 = longestPalindrome3(s3);
System.out.println(palindrome3);
}
}
图解示例
1.暴力解法:
以字符串 “babad” 为例,初始化最长回文串长度 maxLength = 0,最长回文串开始下标 startIndex= 0;
(1)拿到子串 “b”,发现其是回文串,此时更新 maxLength = 1,startIndex = 0;
(2)拿到子串 “ba”,不是回文串,不进行任何操作;
(3)拿到子串 “bab”,是回文串,且长度 currentLength = 3 > maxLength,所以更新 maxLength = 3,startIndex = 0;
(4)拿到子串 “baba”,不是回文串,不进行任何操作;
(5)如此循环,直到外层 i = 0 执行完成之后,变量 maxLength = 3,startIndex = 0;
(6)当 i = 1 时,子串 “a”,“ab”,“aba” 的长度都不大于 maxLength ,所以直接略过,进行子串 “abad” 的比较,因为该子串不是回文串,所以也不需要进行任何操作;
(7)继续循环,当 i = 2,i = 3,i = 4 时,因为其各个子串的长度都不大于 maxLength,所以不需要进行任何操作,所以最终得到的 maxLength = 3,startIndex = 0;
(8)从原始字符串中截取 [0, 3] 区间,即为最终的最长回文子串 “bab”。
2.中心扩散法
以字符串 “bcabbbacd” 为例,初始化最长回文串长度 maxLength = 0,最长回文串开始下标 startIndex= 0;
(1)当 i = 0 时,left = i = 0,right = i = 0,此时拿到字符 ‘b’,因为该字符的下一个或下几个元素都不是 ‘b’,所以以子串 “b” 作为中心点向左右扩散,同时更新下一次循环的起点 i = right + 1;因为该子串是回文串,所以更新 maxLength = 1,startIndex = 0;
(2)当 i = 1 时,left = i = 1,right = i = 1,此时拿到字符 ‘c’,因为该字符的下一个或下几个元素都不是 ‘c’,所以以子串 “c” 作为中心点向左右扩散,同时更新下一次循环的起点 i = right + 1 = 1;因为该子串是回文串但长度不大于 maxLength,所以无需更新 maxLength、startIndex;
(3)当 i = 2 时,left = i = 2,right = i = 2,此时拿到字符 ‘a’,因为该字符的下一个或下几个元素都不是 ‘a’,所以以子串 “a” 作为中心点向左右扩散,同时更新下一次循环的起点 i = right + 1 = 3;因为该子串是回文串但长度不大于 maxLength,所以无需更新 maxLength、startIndex;
(4)当 i = 3 时,left = i = 3,right = i = 3,此时拿到字符 ‘b’,我们发现该字符的后面两个元素都是 ‘a’,此时更新 right = 5,以子串 “bbb” 作为中心点向左右扩散,同时更新下一次循环的起点 i = right + 1 = 6;之后进行扩散操作,扩散过程中需要注意更新 left 和 “right” 的值,扩散之后得到的子串为 “cabbbac”,因为该回文子串的长度大于 maxLength,所以更新 maxLength = 7、startIndex = 1;
(5)下次进行循环时的循环变量 i = 6,拿到的字符为 ‘a’,按照规则进行判断,依次循环,最终得到的最长回文子串便为 “cabbbac”。
3.动态规划法
(1)定义二维数组 dp[length][length],如果 [left, right] 闭区间的字符串为回文串,则 dp[left][right] = true,如果 [left, right] 闭区间的字符串不是回文串,则 dp[left][right] = false;橙色范围即为我们遍历二维数组的范围,垂直遍历,先遍历第一列,再遍历第二列…需要注意的是,遍历时,如果 left 与 right 对应的字符相等且 [left, right] 构成的字符串的长度大于 2 时,dp[left][right] == dp[left + 1][right - 1];如果 left 与 right 对应的字符相等且 [left, right] 构成的字符串的长度小于等于 2 时,则直接更新 dp[left][right] = true;如果 dp[left][right] == true 且 right - left + 1 > maxLegth,则更新 maxLength 和 startIndex;
(2)遍历第一列:dp[0][0] = true,maxLength = 1,startIndex = 0;
(3)遍历第二列:dp[0][1] = false,dp[1][1] = true,right - left + 1 < maxLength,所以无需更新 maxLength 和 startIndex;
(4)遍历第三列:dp[0][2] = false,dp[1][2] = false,dp[2][2] = true,right - left + 1 < maxLength,所以无需更新 maxLength 和 startIndex;
(5)遍历第四列:dp[0][3] = false,dp[1][3] = false,dp[2][3] = false,dp[3][3] = true,right - left + 1 < maxLength,所以无需更新 maxLength 和 startIndex;
(6)遍历第五列:[0, 4] 区间长度大于2,且 0 和 4 下标对应的元素同为 ‘a’,所以此时 dp[0][4] = dp[1][3] = false;dp[1][4] = false,dp[2][4] = false,dp[3][4] = false,dp[4][4] = true,right - left + 1 < maxLength,所以无需更新 maxLength 和 startIndex;
(7)遍历第六列:dp[0][5] = false,dp[1][5] = false,dp[2][5] = false,[3, 5] 区间长度大于2,且 3 和 5 下标对应的元素同为 ‘e’,所以此时 dp[3][5] = dp[4][4] = true,right - left + 1 > maxLength,所以更新 maxLength = 3 和 startIndex = 3;dp[4][5] = false,dp[5][5] = true,right - left + 1 < maxLength,所以无需更新 maxLength 和 startIndex;
(8)遍历第七列:dp[0][6] = false,dp[1][6] = false,[2, 6] 区间长度大于2,且 2 和 6 下标对应的元素同为 ‘b’,所以此时 dp[2][6] = dp[3][5] = true,right - left + 1 > maxLength,所以更新 maxLength = 4 和 startIndex = 3,dp[3][6] = false,dp[4][6] = false,dp[5][6] = false,dp[6][6] = true,right - left + 1 < maxLength,所以无需更新 maxLength 和 startIndex;
(9)遍历第八列:dp[0][7] = false,[1, 7] 区间长度大于2,且 1 和 7 下标对应的元素同为 ‘c’,所以此时 dp[1][7] = dp[2][6] = true,right - left + 1 > maxLength,所以更新 maxLength = 7 和 startIndex = 1,dp[2][7] = false,dp[3][7] = false,dp[4][7] = false,dp[5][7] = false,dp[6][7] = true,dp[7][7] = false,right - left + 1 < maxLength,所以无需更新 maxLength 和 startIndex;
(10)循环完成之后二维矩阵的各个元素为: