问题
给定一个字符串,找到最长的回文连续子字符串。如果不止一个,则返回一个即可。例如,“aabcdcb”的最长回文子串是“bcdcb”。 “bananas”的最长回文子串是“anana”。
中心扩展算法
public static String longestPalindrome(String s) {
if (s == null || s.length() < 1) {
return "";
}
int start = 0;
int end = 0;
for (int i = 0; i < s.length(); i++) {
// 精髓在于此,len1,i与i比较,对应于奇数个数,中心对应i这个数,向两边扩散。aca
// len2,以此类推,对应于偶数个数,中心对应i和i+1中间的空格,相当于已经扩散了一次,已经在i和i+1上,向两边扩散。fddf
int len1 = expandAroundCenter(s, i, i); // 检测长度为奇数的子串, 例如aba
int len2 = expandAroundCenter(s, i, i + 1); // 检测长度为偶数的子串, 例如abba
int len = Math.max(len1, len2);
if (len > end - start) {
// 假设回文串长度为奇数,start与i之间的距离为(2n+1)/2;
// 假设回文串长度为偶数,start与i的距离为(2n)/2-1;
// 而在整数除法里,(2n)/2-1与(2n-1)/2是相等的;(2n+1)/2与(2n)/2的结果是相等的;
// 于是start与i之间的距离得以统一;对于end来说,分析是一致的
start = i - (len - 1) / 2; // 扩展后, 以i为中心,左指针前移一半长度
end = i + len / 2; // 扩展后, 以i为中心,右指针后移一半长度
}
}
return s.substring(start, end + 1);
}
public static int expandAroundCenter(String s, int left, int right) {
//left:相当于指向中间位置的第一位;
//right:相当于指向中间位置最后一位;
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
--left;
++right;
}
// 返回回文数的长度
// 长度为(right - 1) - (left + 1 ) + 1 = right - left - 1
return right - left - 1;
}
动态规划算法
public static String longestPalindrome(String s) {
int len = s.length();
if (len == 1) {
return s;
}
int maxLen = 1;
int begin = 0;
// dp[i][j] 表示 s[i..j] 是否是回文串
boolean[][] dp = new boolean[len][len];
// 初始化:所有长度为 1 的子串都是回文串
for (int i = 0; i < len; i++) {
dp[i][i] = true;
}
char[] charArray = s.toCharArray();
// 递推开始
// 先枚举子串长度,先枚举长度为2的所有子字符串,再依次枚举长度为3,4,5...len所有子字符串
for (int L = 2; L <= len; L++) {
// 枚举左边界,左边界的上限设置可以宽松一些
for (int i = 0; i < len; i++) {
// 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得
int j = L + i - 1;
// 如果右边界越界,就可以退出当前循环
if (j >= len) {
break;
}
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];
}
}
// 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,此时记录回文长度和起始位置
if (dp[i][j] && j - i + 1 > maxLen) {
maxLen = j - i + 1;
begin = i;
}
}
}
return s.substring(begin, begin + maxLen);
}
方法3
public static String longestPalindrome(String s) {
if (s == null || s.length() < 1) {
return "";
}
int len = s.length();
int max = 0;
String res = "";
for (int i = 0; i < len; i++) {
int left = i - 1;
int right = i + 1;
while (left >= 0 && s.charAt(left) == s.charAt(i)) {
left--;
}
while (right < s.length() && s.charAt(right) == s.charAt(i)) {
right++;
}
while (left >= 0 && right < len && s.charAt(left) == s.charAt(right)) {
left--;
right++;
}
// 经过以上判断left和right指向的值一定不同,且包含的子串一定是回文串
if (max < right - left - 1) {
max = right - left - 1;
res = s.substring(left + 1, right);
}
}
return res;
}
方法4
public static String longestPalindrome(String s) {
if (s == null || s.length() < 1) {
return "";
}
int len = s.length();
int max = 0;
int start = 0;
for (int i = 0; i < len; i++) {
int len1 = expandAroundCenter(s, i, i);
int len2 = expandAroundCenter(s, i, i + 1);
int curMax = Math.max(len1, len2);
if (max < curMax) {
max = curMax;
start = i - (curMax + 1) / 2 + 1;
}
}
return s.substring(start, start + max);
}
public static int expandAroundCenter(String s, int left, int right) {
//left:相当于指向中间位置的第一位;
//right:相当于指向中间位置最后一位;
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
--left;
++right;
}
// 返回回文数的长度
// 长度为(right - 1) - (left + 1 ) + 1 = right - left - 1
return right - left - 1;
}