5. 最长回文子串
题目
给你一个字符串 s,找到 s 中最长的回文子串。
示例1
输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。
示例2
输入:s = “cbbd”
输出:“bb”
示例3
输入:s = “a”
输出:“a”
解法1(暴力)
思路
- 列举所有的字串,哦按段是否是回文串,保存最长的回文串。
- 利用两层for循环,时间复杂度为O(n²),for循环中,判断是否是回文串是O(n),所以时间复杂度为O(n³)
public String longestPalindrome(String s) {
String ans = "";
int max = 0;
int len = s.length();
for (int i = 0; i < len; i++)
for (int j = i + 1; j <= len; j++) {
String str = s.substring(i, j);
// 如果str是回文串并且长度大于之前的最长的回文串长度,更新最长长度
if (isPalindromic(str) && str.length() > max) {
ans = s.substring(i, j);
max = ans.length();
}
}
return ans;
}
// 判断是否是回文串
public boolean isPalindromic(String s) {
int len = s.length();
for (int i = 0; i < len / 2; i++) {
if (s.charAt(i) != s.charAt(len - i - 1)) {
return false;
}
}
return true;
}
解法2(动态规划)
思路
- 确定dp数组以及下标的含义
- 布尔类型的dp[i][j]:表示区间范围[i,j]的子串是否是回文子串,true表示是回文子串,false表示不是。
s.charAt(i) != s.charAt(j)
,dp[i][j] = false
;s.charAt(i) == s.charAt(j)
情况1:i和j相同,同一个字符,是回文子串
情况2:i和j相差为2,比如aba,也是回文子串
情况3:i和j相差大于2的时候,例如abcba,此时s.charAt(i) == s.charAt(j)
,就看i到j区间是不是回文子串就看bcb是不是回文子串就可与i了,bcb的区间是i+1于j-1区间,这个区间是不是回文就看dp[i + 1][j -1] 是不是true。
时间复杂度O(n²)
public String longestPalindrome(String s) {
int n = s.length();
if (n < 2) {
return s;
}
int maxStart = 0; //最长回文串的起点
int maxEnd = 0; //最长回文串的终点
int maxLen = 1; //最长回文串的长度
boolean[][] dp = new boolean[n][n];
for (int r = 1; r < n; r++) {
for (int l = 0; l < r; l++) {
if (s.charAt(l) == s.charAt(r) && (r - l <= 2 || dp[l + 1][r - 1])) {
dp[l][r] = true;
if (r - l + 1 > maxLen) {
maxLen = r - l + 1;
maxStart = l;
maxEnd = r;
}
}
}
}
return s.substring(maxStart, maxEnd + 1);
}
解法3(扩展中心)(参考该题解)
思路
- 回文串一定是对称的,所以每次循环选择一个中心,进行左右扩展,判断左右字符是否相等即可。
- 当字符串的长度是奇数时,从每一个字符串开始扩展,是偶数时,从两个字符之间开始扩展, 所以总共有n + n - 1个扩展中心。
时间复杂度O(n²)
public String longestPalindrome(String s) {
int start = 0, end = 0;
for (int i = 0; i < s.length(); i++) {
int len1 = center(s, i, i);
int len2 = center(s, i, i + 1);
int len = Math.max(len1, len2);
if (len > end - start) {
// 更新最长回文串的起点和终点
start = i - (len - 1) / 2;
end = i + len / 2;
}
}
return s.substring(start, end + 1);
}
private int center(String s, int left, int right) {
int Left = left, Right = right;
while (Left >= 0 && Right < s.length() && s.charAt(Left) == s.charAt(Right)) {
// 向左右同时扩散
Left--;
Right++;
}
return Right - Left - 1;
}
解法4(Manacher’s Algorithm)
思路
- 有点没看懂,详情看官方题解。(官方题解)
时间复杂度O(n)
public String longestPalindrome(String s) {
int start = 0, end = -1;
StringBuffer t = new StringBuffer("#");
for (int i = 0; i < s.length(); ++i) {
t.append(s.charAt(i));
t.append('#');
}
t.append('#');
s = t.toString();
List<Integer> arm_len = new ArrayList<Integer>();
int right = -1, j = -1;
for (int i = 0; i < s.length(); ++i) {
int cur_arm_len;
if (right >= i) {
int i_sym = j * 2 - i;
int min_arm_len = Math.min(arm_len.get(i_sym), right - i);
cur_arm_len = expand(s, i - min_arm_len, i + min_arm_len);
} else {
cur_arm_len = expand(s, i, i);
}
arm_len.add(cur_arm_len);
if (i + cur_arm_len > right) {
j = i;
right = i + cur_arm_len;
}
if (cur_arm_len * 2 + 1 > end - start) {
start = i - cur_arm_len;
end = i + cur_arm_len;
}
}
StringBuffer ans = new StringBuffer();
for (int i = start; i <= end; ++i) {
if (s.charAt(i) != '#') {
ans.append(s.charAt(i));
}
}
return ans.toString();
}
public int expand(String s, int left, int right) {
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
--left;
++right;
}
return (right - left - 2) / 2;
}