马拉车算法,我感觉这里的“马拉车”好像是音译过来的,所谓马拉车算法,就是用来求解最大回文数问题的,而最大回文数问题,往往是以字符串为在体的,所以,我们今天来说一说,如何能最高效的找到目标字符串的最大回文数。
1.想一想,如何找到一个字符串的最大回文数?我这里有三种思路:
一.暴力求解,我们可以将一个长度为n的字符找到他的n!个字符串,然后通过遍历来找出这n!个字符串的最大回文数。
时间复杂度:O(n^3);
评价:这样做虽然简单易懂但是代价太高了,不建议使用;
二.字串渐进式求解,首先,我们需要将该字符串进行格式化,在每一个字符之间添加一个特定的标识符(这里我们使用“#”),遍历这个字符串的每一个元素,并且判断每一个元素的最大回文数。
为什么要在每个字符之间添加一个特定的标识符呢?最大回文数有奇数,也有偶数,如果出现“abccba”这种情况,如果不添加标识符,就无法得到正确结果,添加标识符,就是为了消除这种情况。
时间复杂度:O(n^2);
三.也就是我们进今天的主家了,马拉车算法:
看看上面这个字符串,我们可以轻易的看出第五位式最长回文数的中心,最长回文数的长度是7,没错,这些确实可以轻易的得出,那么,我们还可以看出什么呢?我们还可以看出以第五位为轴,2-4,6-8对称。我们看完了第五位,看第六位,第六位的最长回文数是多少呢?一眼看出是1,有没有不用眼睛就能知道是1的方法呢?有!
我们可已发现,和点6对称的点4的最长回文数也是1,为什么呢?因为他们关于点5对称。而且点4的最大回文数是1,而且
2-4,6-8是堆成的,所以我们不需要关心点6后面的字符,直接由点4提供给我们的信息直接得出点六的最大回文数。
那么我们继续往后走,到了点7,我们可以知道点7对应的是点3,而点3的最长回文数是3,而且点三的回文字符串正好包含于2-4中,所以我们可以断定,点7的最长回文数必定大于或等于3。为什么是大于或等于而不直接让它等于呢?因为我们根据堆成知道了6-8关于点7对称,但是我们不知道第九位是什么值,所以无法判断,那么这时候我们要怎么办呢?我们这时候就需要判断第九位和第五位是否相同了,如果相同,那么我们就将再一次像利用点5的对称性一样利用点7的对称性来判断电8,甚至点9....。
总结,该算法就是通过利用前面已得到的字符的最大回文数的对称性来为后续的判断提供方便,在最理想的时候,该算法的时间复杂度可降低到O(n);
下面是本人利用Java实现的代码。
public class PlalindromeString {
/**
* 判断一个字符串是不是回文数字符串(本算法中用不到,思维其实很简单)
* @param str
* @return
*/
private boolean IsPlalindromeString(String str) {
//获取字符串的长度
int len=str.length();
for(int i=0;i<len/2;i++) {
if(str.charAt(i)!=str.charAt(len-i-1)) {
return false;
}
return true;
}
return false;
}
/**
*对字符串进行预处理,目的事为了将回文数长读为奇数和偶数的情况整合起来一起处理。
* @param str
* @return
*/
private String addSign(String str) {
StringBuffer sb=new StringBuffer("");
sb.append("#");
int len=str.length();
for(int i=0;i<len;i++) {
sb.append(str.charAt(i));
sb.append("#");
}
return sb.toString();
}
/**
* 寻找最大回文数(马拉车算法)
* @param str
* @return
*/
private String findLongestPlalindromeString(String str) {
//首先的到字符穿的长度;
int len=str.length();
//当前最大回文数的长度
int longgest=0;
//最大回文数对应的中心点
int center=0;
//当前节点回文数的中心点所在位置
int centerAddress=0;
//当前最大回文数的右臂展(也就是它的右边界)
int rightSide=0;
//定义一个数组用来保存以每一个字符为中心的最大回文数长度
int[] LenArr=new int[len];
/**
* 进入核心算法阶段
*/
for(int i=0;i<len;i++) {
//设置init变量判断当前字符是否需要向右扩展
boolean init=true;
//如果当前位置i小于右边界
if(i<rightSide) {
//计算对应位置
int length=2*centerAddress-i;
//如果以中心的为对称中心且当前位置的对称点的LenArr[]+当前位置<右边界,则无需向右扩展,直接让其对称点的LenArr值等于该点的LenArr值
if(LenArr[length]+i<rightSide) {
LenArr[i]=LenArr[length];
init=false;
}
//若果以当前点为对称中心且目标点的对称点的LenArr[]+当前位置>右边界,则需要向右扩展,变更中心点。
if(LenArr[length]+i>=rightSide) {
LenArr[i]=rightSide-i;
}
}
//右边界移动
if(init) {
for(int j=i+LenArr[i]+1;i+LenArr[i]+1<len && i-LenArr[i]-1>=0 ;j++) {
if(str.charAt(j)==str.charAt(2*i-j)) {
LenArr[i]=LenArr[i]+1;
}else {
break;
}
}
centerAddress=i;
rightSide=i+LenArr[i];
//判断以当前点为中心的回文数串的长度是否大于已记录的最大回文数的长度,如果大于,那么我们就将其记录
if(LenArr[i]>longgest) {
longgest=LenArr[i];
center=i;
}
}
}
StringBuffer sb=new StringBuffer("");
for(int i1=center-LenArr[center];i1<center+LenArr[center];i1++) {
if(str.charAt(i1)!='#') {
sb.append(str.charAt(i1));
}
}
return sb.toString();
}
public static void main(String[] args) {
String[] testStrArr = new String[] {
"abcdcef",
"adaelele",
"cabadabae",
"aaaabcdefgfedcbaa",
"aaba",
"aaaaaaaaa"
};
PlalindromeString pls=new PlalindromeString();
//先格式化字符串
for(String str:testStrArr) {
System.out.println(pls.findLongestPlalindromeString(pls.addSign(str)));
}
}
}
/*
测试结果
原字串 : abcdcef
最长回文串 : cdc
原字串 : adaelele
最长回文串 : elele
原字串 : cabadabae
最长回文串 : abadaba
原字串 : aaaabcdefgfedcbaa
最长回文串 : aabcdefgfedcbaa
原字串 : aaba
最长回文串 : aba
原字串 : aaaaaaaaa
最长回文串 : aaaaaaaaa
*/