题目叙述
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 1:
输入: “babad
”
输出: “bab”
注意: “aba” 也是一个有效答案。
示例 2:
输入: “cbbd”
输出: “bb”
题解
方法一:暴力法
截取出输入字符串的所有子串,并判断是否是回文子串,在找到最大的那个
private static String f(String s)
{
String max = "";
for(int i = 0; i < s.length(); i++)
{
for(int j = i + 1; j < s.length() + 1; j++)
{
String s_son = s.substring(i, j);
if(check(s_son))
{
if(s_son.length() > max.length())
max = s_son;
}
}
}
return max;
}
private static boolean check(String s_son)
{
int left = 0;
int right = s_son.length() - 1;
while(left < right)
{
if(s_son.substring(left, left + 1).equals(s_son.subSequence(right, right + 1)))
{
left++;
right--;
}
else
return false;
}
return true;
}
复杂度分析:
1.时间复杂度:O(n^3)
2.空间复杂度:O(1)
方法二:从中心向两边发散查找
假设一个字符串str是回文串,那么它一定是左右对称的,所以该字符串以某个字符str[mid]为中心的前缀和后缀一定是相同的。例如,对于回文串"aba",以’b’为中心,它的前缀和后缀都是’a’。故,可以枚举字符串的每个字符作为回文串的中心位置,左右扩展得到回文串,然后比较得到最长的长度。
private static String f2(String s)
{
int maxLen = 1;
int start = 0;
//求奇回文子串
for(int i = 0; i < s.length() + 1; i++)
{
int left = i - 1;
int right = i + 1;
while(left >= 0 && right <s.length()
&& s.substring(left, left + 1).equals(s.substring(right, right + 1)))
{
if(right - left + 1 > maxLen)
{
maxLen = right - left + 1;
start = left;
}
left--;
right++;
}
}
//求偶回文子串
for(int i = 0; i < s.length(); i++)
{
int left = i;
int right = i + 1;
while(left >= 0 && right <s.length()
&& s.substring(left, left + 1).equals(s.substring(right, right + 1)))
{
if(right - left + 1 > maxLen)
{
maxLen = right - left + 1;
start = left;
}
left--;
right++;
}
}
if(s.equals(""))
return "";
else
return s.substring(start, maxLen + start);
}
复杂度分析:
1.时间复杂度:O(n^2)
2.空间复杂度:O(1)
方法三:动态规划
对于字符串s,假设dp[i,j]=1表示str[i…j]是回文子串,那个必定存在dp[i+1,j-1]=1。这样最长回文子串就能分解成一系列子问题,可以利用动态规划求解了。
private static String f3(String s)
{
int start = 0;
int maxLen = 1;
int [][] dp = new int[s.length()][s.length()];
for(int i = 0; i < s.length(); i++)
{
dp[i][i] = 1;
if(i < s.length() - 1)
{
if(s.substring(i, i + 1).equals(s.substring(i + 1, i + 2)))
{
dp[i][i+1] = 1;
start = i;
maxLen = 2;
}
}
}
for(int len = 3; len <= s.length(); len++)
{
for(int i = 0; len + i - 1 < s.length(); i++)
{
int j = len + i - 1;
if(s.substring(i, i + 1).equals(s.substring(j, j + 1)) && dp[i + 1][j - 1] == 1)
{
dp[i][j] = 1;
start = i;
maxLen = len;
}
}
}
if(s.length() == 1 || s.equals(""))
return s;
else
return s.substring(start, maxLen + start);
}
复杂度分析:
1.时间复杂度:O(n^2)
2.空间复杂度:O(n^2)
方法四:Manacher算法
1.对字符串s进行预处理,在每个字符的两端插入一个相同的不会再s中出现的字符,得到一个新的字符串s_new。
2.对字符串s_new每个字符遍历,以每个字符为中心向两端查找最大的回文子串。
(1)假设计算到位置i 的字符charArr[i], 在i之前位置的计算过程中, 都会不断地更新pR 和index的值,即位置i 之前的index 这个回文中心扩出了一个最右的回文边界pR。
(2)如果pR-1 位置没有包住当前的i 位置。比如“#c#a#b#a#c#”,计算到charArr[1]==’c’时,pR 为1。也就是说,右边界在1 位置,1 位置为最右回文半径即将到达但还没有达到的位置, 所以当前的pR-1 位置没有包住当前的i 位置。此时和普通做法一样,从i 位置字符开始,向左右两侧扩出去检查, 此时的“ 扩” 过程没有获得加速。
(3) 如果pR -1 位置包住当前的i 位置。比如“#c#a#b#a#c#”, 计算到charArr [6 … 10] 时,pR 都为11,此时pR-1 包住了位置6-10。这种情况下,检查过程是可以获得优化的,这也是manacher 算法的核心内容。(百度百科)
以下代码来自百度百科
if (str == null || str.length() == 0) {
return null;
}char[] charArr = manacherString(str);
int[] pArr = new int[charArr.length];
int index=-1;
int pR=-1;
int maxContainsEnd = -1;
for (int i = 0; i ! = charArr.length; i++) {
pArr [i] = pR > i ? Math.min (pArr [2 *index - i], pR - i) : 1;
while (i + pArr[i] < charArr.length && i- pArr[i] > -1) {
if (charArr [i + pArr [i]] == charArr[i - pArr[i]])
pArr[i]++;
else {
break;
}
}if (i + pArr[i] > pR) {pR = i + pArr[i];
index = i;
}if (pR == charArr.length) {
maxContainsEnd = pArr[i];
break;
}
}
char [] res = new char [str.length () -maxContainsEnd + 1];
for (int i = 0; i < res.length; i++) {
res[res.length - 1 - i] = charArr [i * 2+ 1];
} return String.valueOf(res);
复杂度分析:
1.时间复杂度:O(n)
2.空间复杂度:O(n)