最长回文串
题目:
给你一个字符串 s,找到 s 中最长的回文子串。
Input:
“babad”
Output:
“aba"或者"bab”
暴力解法
不能AC解法很容易就想到两重循环,提取所有可能的字符串,然后判断是否是回文串
class Solution {
public String longestPalindrome(String s) {
int len = 0;//比较值
String last = "";//输出值
for(int i = 0;i < s.length();i++)
{
for(int j = i;j <= s.length();j++)
{
String b;
b = s.substring(i,j);//提取字符串
StringBuffer c = new StringBuffer(b);
if(c.reverse().toString().equals(b))//判断是否为回文数
{
if(b.length()>len)
{
len = b.length();
last = b;
}
}
}
}
return last;
}
}
很遗憾超时。reserve的时间复杂度O(n)
时间复杂度:O(n^3)
最长公共子串
用二维数组,例如babad,转置dabab,注意aba转置后没有变,所以定义一二维数组,起始排相等定义1,以后再相似便加一,找到最长的相等字符串,并定位其下标。
- | b | a | b | a | d |
---|---|---|---|---|---|
d | 0 | 0 | 0 | 0 | 1 |
a | 0 | 1 | 0 | 1 | 0 |
b | 1 | 0 | 2 | 0 | 0 |
a | 0 | 2 | 0 | 3 | 0 |
b | 1 | 0 | 3 | 0 | 0 |
应该不能AC解法:找到最长公共子串长度,既3,所以推出是aba和bab
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][length];
int maxLen = 0;//记录长度
int maxEnd = 0;//记录下标
for (int i = 0; i < length; i++)
for (int j = 0; j < length; j++) {
if (origin.charAt(i) == reverse.charAt(j)) {
if (i == 0 || j == 0) {
arr[i][j] = 1;
} else {
arr[i][j] = arr[i - 1][j - 1] + 1;
}
}
if (arr[i][j] > maxLen) {
maxLen = arr[i][j];
maxEnd = i; //以 i 位置结尾的字符
}
}
}
return s.substring(maxEnd - maxLen + 1, maxEnd + 1);
}
为什么说不能AC呢?例如字符串“ab51ba”,把二维数组打出来
- | a | b | 5 | 1 | b | a |
---|---|---|---|---|---|---|
a | 1 | 0 | 0 | 0 | 0 | 1 |
b | 0 | 2 | 0 | 0 | 1 | 0 |
1 | 0 | 0 | 0 | 1 | 0 | 0 |
5 | 0 | 0 | 1 | 0 | 0 | 0 |
b | 0 | 1 | 0 | 0 | 1 | 0 |
a | 1 | 0 | 0 | 0 | 0 | 2 |
可以找到ab和ba是最长公共子串,但是都不是回文串,注意观察,我们注意到第一个2所定位的b,它在未倒装之前,在字符串的位置并不是列标所对应的1,而是后面的4,所以并不是同一个字符。那我们只用加一个判断,判断最后一个字符,再更新上面代码Maxlen和Maxend。字符串长度 — 行标 —1 + Maxlen — 1 = 列标?
加上代码块
int beforeRev = length - 1 - j; if (beforeRev + arr[i][j] - 1 == i) //判断下标是否对应
对于空间的优化: 用二维数组不难发现,其实都是上一行来推出下一行,所有我们可以用一维数组来进行空间的优化
class Solution {
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^2) | 空间复杂度:O(n)
中心扩展
偷偷地盗了张图,由图看,我们可以找一个中心,然后双指针左右移动,如果指向字符相等的话,就说明是回文的,有n个数,数为中心和两数中间为中心,共有n+n-1个中心。
例如a所在中心是2,向左右扩展,得到ababa,得到出不包括中心的最长长度为4然后根据中心来推出起始和终末。
class Solution {
public String longestPalindrome(String s) {
if(s == null)
return "";
int start = 0,end = 0;//定位下标
for(int i = 0;i < s.length();i++)
{
int len1 = expandAroundCenter(s, i, i);
int len2 = expandAroundCenter(s, i, i + 1);
int len = Math.max(len1, len2);//找出以i为中心的最长回文串的长度,不包括中心点
if (len > end - start) {
start = i - (len - 1) / 2;
end = i + len / 2;
}
}
return s.substring(start,end+1);
}
public int expandAroundCenter(String s,int L,int R){
while(L >= 0&&R < s.length() && s.charAt(L) == s.charAt(R))
{
L--;
R++;
}
return R - L - 1;
}
}
时间复杂度:O(n^2) | 空间复杂度:O(1)
Manacher’s Algorithm 马拉车算法。
1975年由Manacher前辈发明。
链接: link1.
链接: link2.
根据中心拓展法,中心有两种,奇数,偶数,但是我们在字符串中插入#
如ababd,插入字符#,并且用^KaTeX parse error: Expected group after '^' at position 11: 来控制始末。得到" ^̲#a#b#a#b#d#"保证字符串的个数永远是奇数。然后想要一个辅助数组P[i],存入,以i为中心的拓展长度,找到最大的数,便是最长的回文串长度,然后根据中心i推出start,end。向之前一样,输出。
例如6c的p[i]是5,就是向左和向右拓展为五,而去掉#,最长回文串刚好是五
当然最主要的还是求p[i]:
方法一当然是以每一个字符为中心,然后用中心拓展求出辅助数组p[i],应该是求得出来的
方法二,我看了半天,后面从别的地方了解到,是根据前一部分求出的数组来更新
例如求?处的p[i]它的初始值可以是镜像对称的值,但是可能它的镜像p[i_mirror]是大于往右拓展的值的,例如p[i_mirror] = 4,但是i不能再向右拓展4,所以用两者最小的为初始值,然后再根据这个中心拓展,是不是能够找到更长的回文串while (T.charAt(i + 1 + P[i]) == T.charAt(i - 1 - P[i])) { P[i]++; }
然后再更新C和R。
剩下的不多说了吧,找到最大值,然后定位start,end,截取字符串,输出。
import java.util.*;
class Solution {
public String preProcess(String s) {
int n = s.length();
if (n == 0) {
return "^$";
}
String ret = "^";
for (int i = 0; i < n; i++)
ret += "#" + s.charAt(i);
ret += "#$";
return ret; }
// ⻢拉⻋算法
public String longestPalindrome(String s) {
String T = preProcess(s);
int n = T.length();
int[] P = new int[n];
int C = 0, R = 0;
for (int i = 1; i < n - 1; i++) {
int i_mirror = 2 * C - i;
if (R > i) {
P[i] = Math.min(R - i, P[i_mirror]);// 防⽌超出 R
} else {
P[i] = 0;// 等于 R 的情况
}
// 碰到之前讲的三种情况时候,需要利⽤中⼼扩展法
while (T.charAt(i + 1 + P[i]) == T.charAt(i - 1 - P[i])) {
P[i]++;
}
// 判断是否需要更新 R
if (i + P[i] > R) {
C = i;
R = i + P[i];
}
}
// 找出 P 的最⼤值
int maxLen = 0;
int centerIndex = 0;
for (int i = 1; i < n - 1; i++) {
if (P[i] > maxLen) {
maxLen = P[i];
centerIndex = i;
}
}
int start = (centerIndex - maxLen) / 2; //最开始讲的求原字符串下标
return s.substring(start, start + maxLen);
}
}
以上是看了沉默王二的题解和部分力扣上的题解所写,算是笔记本好了