题目来自LeetCode,链接:最长回文子串。给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例1:
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
示例2:
输入: "cbbd"
输出: "bb"
首先最简单的就是暴力法了,比较简单就不多讲了,注意从长度较长往较短的方向遍历就好了。时间复杂度是
O
(
n
3
)
O(n^{3})
O(n3),空间复杂度这里因为把字符串转为了数组所以是
O
(
n
)
O(n)
O(n),当然不转直接用charAt()
去获取各位置上的字符也是可以的,空间复杂度就是
O
(
1
)
O(1)
O(1)了。
JAVA版代码如下:
class Solution {
public String longestPalindrome(String s) {
if (s.length() == 0) {
return "";
}
char[] chars = s.toCharArray();
for (int i = chars.length; i > 1; --i) {
for (int j = 0; j <= chars.length - i; ++j) {
boolean match = true;
for (int k = 0; k < i / 2; ++k) {
if (chars[j + k] != chars[j + i - k - 1]) {
match = false;
break;
}
}
if (match) {
return new String(chars, j, i);
}
}
}
return new String(chars, 0, 1);
}
}
提交结果如下:
![](https://i-blog.csdnimg.cn/blog_migrate/e99f7610b27eb86dc600d2069b07f27c.png)
接着再看怎么进一步减少时间复杂度,考虑到一个字符串s[i:j]
为回文串的条件是s[i]==s[j]
且s[i+1:j-1]
也为回文串,所以可以用动态规划来做。定义dp[i][j]
表示字符串从i
到j
这一段是否为回文串,那么dp[i][j] = s[i]==s[j] && (j-1<i+1 || dp[i+1][j-1])
,这里j-1<i+1
的意思是字符串的长度为1或2的情况下只要s[i]==s[j]
就是回文串,否则就需要dp[i+1][j-1]=true
。这种方法的时间复杂度为
O
(
n
2
)
O(n^{2})
O(n2),空间复杂度为
O
(
n
2
)
O(n^{2})
O(n2)。
JAVA版代码如下:
class Solution {
public String longestPalindrome(String s) {
if (s.length() == 0) {
return "";
}
char[] chars = s.toCharArray();
int N = chars.length;
// dp[i][j]表示从i到j这一段是回文串
boolean[][] dp = new boolean[N][N];
int left = 0, maxLen = 0;
for (int i = N - 1; i >= 0; --i) {
for (int j = i; j < N; ++j) {
dp[i][j] = (chars[i] == chars[j]) && (j - 1 < i + 1 || dp[i + 1][j - 1]);
if (dp[i][j] && j - i + 1 > maxLen) {
maxLen = j - i + 1;
left = i;
}
}
}
return new String(chars, left, maxLen);
}
}
提交结果如下:
![](https://i-blog.csdnimg.cn/blog_migrate/6f871940bb0b1dfbf7c0691e925ea718.png)
同样,每次这种动态规划的题目看到状态只与前几个状态有关的我们总是可以进一步减少空间复杂度,这里可以只用一个长度为n
的数组配合一个变量来保存状态,将空间复杂度降到
O
(
n
)
O(n)
O(n)。
JAVA版代码如下:
class Solution {
public String longestPalindrome(String s) {
if (s.length() == 0) {
return "";
}
char[] chars = s.toCharArray();
int N = chars.length;
// dp[i][j]表示从i到j这一段是回文串
boolean[] dp = new boolean[N];
int left = 0, maxLen = 0;
for (int i = N - 1; i >= 0; --i) {
boolean dp_i_1 = true;
for (int j = i; j < N; ++j) {
boolean temp = dp[j];
dp[j] = (chars[i] == chars[j]) && (j - 1 < i + 1 || dp_i_1);
dp_i_1 = temp;
if (dp[j] && j - i + 1 > maxLen) {
maxLen = j - i + 1;
left = i;
}
}
}
return new String(chars, left, maxLen);
}
}
提交结果如下:
![](https://i-blog.csdnimg.cn/blog_migrate/14ffab28e7e4cac33ca7474479bb169e.png)
接着这里再用另一种思路,找回文串的时候是从字符串中的某个点(这个点可以是某个字符比如aba
中的b
或者某两个字符之间的空白比如abba
的bb
之间的空白作为出发点)出发,往两边延伸找到以当前点为中心的最大回文串。这种方法的时间复杂度还是
O
(
n
2
)
O(n^{2})
O(n2),空间复杂度看实现可能有所不同,比如下面的代码因为把字符串保存为字符数组以加速所以空间复杂度是
O
(
n
)
O(n)
O(n),但如果用charAt()
就可以达到
O
(
1
)
O(1)
O(1)的空间复杂度。
JAVA版代码如下:
class Solution {
private char[] chars;
private int maxLength(int left, int right) {
while (left >= 0 && right < chars.length && chars[left] == chars[right]) {
--left;
++right;
}
return right - left - 1;
}
public String longestPalindrome(String s) {
if (s.length() == 0) {
return "";
}
chars = s.toCharArray();
int middleIdx = 0, maxLen = 0;
for (int i = 0; i < chars.length; ++i) {
int longer = Math.max(maxLength(i, i), maxLength(i, i + 1));
if (longer > maxLen) {
maxLen = longer;
middleIdx = i;
}
}
int startIdx = maxLen % 2 == 0 ? middleIdx - maxLen / 2 + 1 : middleIdx - maxLen / 2;
return new String(chars, startIdx, maxLen);
}
}
提交结果如下:
![](https://i-blog.csdnimg.cn/blog_migrate/915d7751cc74034cd97bedbf7d2c11d7.png)
最后介绍一下时间复杂度和空间复杂度均为 O ( n ) O(n) O(n)的马拉车算法(Manacher’s Algorithm),具体算法介绍可以看这里。
JAVA版代码如下:
class Solution {
public String longestPalindrome(String s) {
int L = s.length();
if (L == 0) {
return "";
}
char[] chars = new char[L * 2 + 3];
int[] P = new int[L * 2 + 3];
chars[0] = '^';
chars[chars.length - 1] = '$';
chars[chars.length - 2] = '#';
for (int i = 0; i < L; ++i) {
chars[2 * i + 1] = '#';
chars[2 * i + 2] = s.charAt(i);
}
int center = 0, right = 0;
int maxIdx = 0, maxLen = 0;
for (int i = 1; i < chars.length - 1; ++i) {
if (i < right) {
P[i] = Math.min(P[2 * center - i], right - i);
}
else {
P[i] = 0;
}
while (chars[i + P[i] + 1] == chars[i - P[i] - 1]) {
++P[i];
}
if (i + P[i] > right) {
right = i + P[i];
center = i;
}
if (P[i] > maxLen) {
maxLen = P[i];
maxIdx = i;
}
}
int startIdx = (maxIdx - P[maxIdx] - 1) / 2;
return s.substring(startIdx, startIdx + maxLen);
}
}
提交结果如下:
![](https://i-blog.csdnimg.cn/blog_migrate/c1a8e70e4b6222491f3d1ef6cc29716e.png)
Python版代码如下:
class Solution:
def longestPalindrome(self, s: str) -> str:
extendStr = '^'
for ss in s:
extendStr += '#' + ss
extendStr += '#$'
P = [0 for _ in range(len(extendStr))]
center, right = 0, 0
maxIdx, maxLen = 0, 0
for i in range(1, len(extendStr) - 1):
if i < right:
P[i] = min(P[2 * center - i], right - i)
else:
P[i] = 0
while extendStr[i - P[i] - 1] == extendStr[i + P[i] + 1]:
P[i] += 1
if i + P[i] > right:
right = i + P[i]
center = i
if P[i] > maxLen:
maxLen = P[i]
maxIdx = i
startIdx = (maxIdx - P[maxIdx] - 1) // 2
return s[startIdx : startIdx + maxLen]
提交结果如下:
![](https://i-blog.csdnimg.cn/blog_migrate/227a338bb9a098c15dc7d1603f3408a6.png)