解法 1:暴力破解
暴力求解,列举所有的子串,判断是否为回文串,保存最长的回文串。
代码
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;
}
// 暴力解法
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 test = s.substring(i, j);
if (isPalindromic(test) && test.length() > max) {
ans = s.substring(i, j);
max = Math.max(max, ans.length());
}
}
return ans;
}
时间复杂度:两层 for
循环 O(n²),for
循环里边判断是否为回文 O(n),所以时间复杂度为 O(n³)。
空间复杂度:O(1),常数个变量。
解法 2: 最长公共子串
根据回文串的定义,正着和反着读一样,那我们是不是把原来的字符串倒置了,然后找最长的公共子串就可以了。例如 S = "caba"
,S = "abac"
,最长公共子串是 "aba"
,所以原字符串的最长回文串就是 "aba"
。
注意:
求出的最长公共子串,并不一定是回文串,我们还需要判断该字符串倒置前的下标和当前的字符串下标是不是匹配。例如,S="abc435cba"
,S="abc534cba"
,最长公共子串是 "abc"
和 "cba"
,但很明显这两个字符串都不是回文串。
代码:
public String longestPalindrome(String s) {
if (s.equals(""))
return "";
String origin = s;
String reverse = new StringBuffer(s).reverse().toString();
int length = s.length();
int[] arr = new int[length];
int maxLen = 0;
int maxEnd = 0;
for (int i = 0; i < length; i++)
/**************修改的地方***************************/
for (int j = length - 1; j >= 0; j--) {
/**************************************************/
if (origin.charAt(i) == reverse.charAt(j)) {
if (i == 0 || j == 0) {
arr[j] = 1;
} else {
arr[j] = arr[j - 1] + 1;
}
/**************修改的地方***************************/
//之前二维数组,每次用的是不同的列,所以不用置 0 。
} else {
arr[j] = 0;
}
/**************************************************/
if (arr[j] > maxLen) {
int beforeRev = length - 1 - j;
if (beforeRev + arr[j] - 1 == i) {
maxLen = arr[j];
maxEnd = i;
}
}
}
return s.substring(maxEnd - maxLen + 1, maxEnd + 1);
}
时间复杂度 O(n²)。
空间复杂度降为 O(n)。
解法 3:中心扩散法
中心扩散法找回文串:
从每一个位置出发,向两边扩散即可。遇到不是回文的时候结束。举个例子,str=acdbbdaa 我们需要寻找从第一个 b
(位置为 3)出发最长回文串为多少。怎么寻找?
首先往左寻找与当期位置相同的字符,直到遇到不相等为止。
然后往右寻找与当期位置相同的字符,直到遇到不相等为止。
最后左右双向扩散,直到左和右不相等。如下图所示:
每个位置向两边扩散都会出现一个窗口大小(len
)。如果 len>maxLen
(用来表示最长回文串的长度)。则更新 maxLen
的值。
因为我们最后要返回的是具体子串,而不是长度,因此,还需要记录一下 maxLen
时的起始位置(maxStart
),即此时还要 maxStart=len
。
代码:
public String longestPalindrome1(String s) {
if (s == null || s.length() == 0) {
return "";
}
int strLen = s.length();
int left = 0;
int right = 0;
int len = 1;
int maxStart = 0;
int maxLen = 0;
for (int i = 0; i < strLen; i++) {
left = i - 1;
right = i + 1;
while (left >= 0 && s.charAt(left) == s.charAt(i)) {
len++;
left--;
}
while (right < strLen && s.charAt(right) == s.charAt(i)) {
len++;
right++;
}
while (left >= 0 && right < strLen && s.charAt(right) == s.charAt(left)) {
len = len + 2;
left--;
right++;
}
if (len > maxLen) {
maxLen = len;
maxStart = left;
}
len = 1;
}
return s.substring(maxStart + 1, maxStart + maxLen + 1);
}
时间复杂度:O(n²)。
空间复杂度:O(1)。
解法 4:优化(动态规划):
中心扩散的方法,其实做了很多重复计算。动态规划就是为了减少重复计算的问题。动态规划听起来很高大上。其实说白了就是空间换时间,将计算结果暂存起来,避免重复计算。
我们用一个 boolean dp[l][r]
表示字符串从 i
到 j
这段是否为回文。试想如果 dp[l][r]=true
,我们要判断 dp[l-1][r+1]
是否为回文。只需要判断字符串在(l-1
)和(r+1
)两个位置是否为相同的字符,是不是减少了很多重复计算。
进入正题,动态规划关键是找到初始状态和状态转移方程。
初始状态,l=r
时,此时 dp[l][r]=true
。
状态转移方程,dp[l][r]=true
并且(l-1
)和(r+1
)两个位置为相同的字符,此时 dp[l-1][r+1]=true
。
代码:
public String longestPalindrome(String s) {
if (s == null || s.length() < 2) {
return s;
}
int strLen = s.length();
int maxStart = 0; //最长回文串的起点
int maxEnd = 0; //最长回文串的终点
int maxLen = 1; //最长回文串的长度
boolean[][] dp = new boolean[strLen][strLen];
for (int r = 1; r < strLen; 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;
}
}
时间复杂度:O(n²)。
空间复杂度:O(n)。