题目描述:
一.中心扩展法
考虑到回文串是沿中心轴对称的,所以沿轴向两端扩展。
但是有个小问题,当回文数为偶数时,没有轴,所以要分奇偶扩展两次,效率低下。
分奇偶扩展 :
class Solution {
public String longestPalindrome(String s) {
int len = s.length();
if(len<1) {
return s;
}
int start=0,end=0;
for(int i=0;i<len;i++) {
int len1 = expand(s, i, i);
int len2 = expand(s, i, i+1);
int L = Math.max(len1, len2);
if(L>end-start) {
start = i - (L-1)/2;
end = i + L/2;
}
}
return s.substring(start, end+1);
}
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 - 1;
}
}
对原字符串略加修改:cbbd 变为 #c#b#b#d#,那么任何一个回文串的中心轴都有字符了
并且修改后的回文串有如下特性:1. 回文串的开始和结尾一定是 # ;
2.每个非 # 字符的下标除二得该字符在原字符串中的下标;
3.回文直径/2 == 回文半径 - 1 == 原串的回文子串的长度
(不考虑奇偶)只需一次扩展:
class Solution {
int len,Len;
String str;
public String preProcess(String s) {
String str = "#";
for(int i=0;i<len;i++) {
str += s.charAt(i) + "#";
}
return str;
}
public String longestPalindrome(String s) {
len = s.length();
if(len<1) {
return s;
}
str = preProcess(s);
Len = str.length();
int start=0,end=0;
for(int i=0;i<Len;i++) {
int len1 = expand(s, i, i);
if(len1>end-start) {
start = i - (len1-1)/2;
end = i + len1/2;
}
}
return s.substring((start+1)/2, (end-1)/2+1);
}
public int expand(String s,int left,int right) {
while(left >= 0 && right < Len && str.charAt(left)==str.charAt(right)) {
left--;
right++;
}
return right - left - 1;
}
}
二.动态规划法
转移方程:每个最长回文子串都可以追朔到s[i] = s[i] 或者 s[i] = s[i+1]
我们用一个数组来记录两个字符之间是否能形成回文,boolean[][] dp = new boolean[len][len];
算法核心:
完整代码:
class Solution {
public String longestPalindrome(String s) {
int len = s.length();
if(len<2) {
return s;
}
boolean[][] dp = new boolean[len][len];
int maxLen = 1,begin=0; //回文串最大长度,回文串起始点
for(int i=0;i<len;i++) {
dp[i][i] = true;
}
//L指当前子串长度(步长),最短为2,最长为s.length()
for(int L=2;L<=len;L++) {
for(int i=0;i<len;i++) {
int j = i+L-1; //右端点j
if(j>=len) { //防止右端点超出范围
break;
}
if(s.charAt(i)!=s.charAt(j)) { //不相等则直接false
dp[i][j] = false;
}else { //相等只需判断内部子串是否回文
if(j-i<3) {
dp[i][j] = true; //当子串长度小于4必定回文
}else {
dp[i][j] = dp[i+1][j-1]; //转移方程回溯
}
}
if(dp[i][j]&&j-i+1>maxLen) {
maxLen = j-i+1;
begin = i;
}
}
}
return s.substring(begin, begin+maxLen);
}
}
三.Manacher算法(马拉车)
1.明确几个概念:
回文半径,回文直径,中心点:例:#a#b#a# 回文半径为4,回文直径为7,中心点为b;
最右回文右边界:例:#a#c#a#v#a#b#a#
2.情况分类:
3.完整代码:
class Solution {
public static final int Min = -9999999;
int len;
public String preProcess(String s) {
String str = "#";
for(int i=0;i<len;i++) {
str += s.charAt(i) + "#";
}
return str;
}
public String longestPalindrome(String s) {
int max = Min,begin=-1;
len = s.length();
if(len<1) {
return s;
}
String string = preProcess(s);
int L = string.length();
int[] P = new int[L]; //用来记录每个中心点对应的回文半径
int R=-1,center=-1; //R表示扩成功位置的下一个位置
for(int i=0;i<L;i++) {
//前者i在R内,三种小情况i*回文半径在LR中,则取P[2*center - i],不在取R-i,
//压边只可能扩大,扩大情况后续代码考虑
P[i] = R>i ? Math.min(P[2*center - i], R-i):1;
//扩展代码
while(i + P[i] < L&&i - P[i] > -1) {
if(string.charAt(i + P[i])==string.charAt(i - P[i])) {
P[i]++;
}else {
break;
}
}
if(i + P[i] > R) { //越界的话,更新center和R
center = i;
R = i+P[i];
}
if(P[i] > max) {
max = P[i];
begin = i - P[i] + 1; //begin为修改后的串的回文子串的第一个字符的下标
//(begin+1)/2为第一个字符在原串中的下标
}
}
return s.substring((begin+1)/2, (begin+1)/2 + max - 1);
}
}