最长回文的五种实现(js代码实现)

原创 2014年09月23日 14:46:46

题目

给定字符串,找到它的最长回文子串,都有哪些思路呢?例如"adaiziguizhongrenenrgnohziugiziadb",回文字串很多了,但最长的是"daiziguizhongrenenrgnohziugiziad"。

解题思路

思路1:

暴力法,外面的两层循环找到所有子串,第三层循环判断子串是否是回文。方法的时间复杂度为O(n^3),空间复杂度为O(1)。

代码实现:

function makeOdd(str){
	var len = str.length;
	if(len % 2 === 1){
		return str;
	}
	var newStr = '#';
	for(i = 0;i<len;i++){
		newStr += str[i]+'#';
	}
	return newStr;
}
function judge(str){
	(str.length%2 === 0) && (str = makeOdd(str));
	var len = str.length,
		half = Math.floor(len/2),
		last = len-half;
	var i = 0;
	while(i<=last){
		if(str[half-i] !== str[half+i]){
			return false;
		}
		i++;
	}
	return true;
}
function getAllSubs(str){
	var len = str.length,
		res = [];
	for(var i = 0;i<len;i++){
		for(var j = i;j<len;j++){
			var sub = str.substring(i,j+1);
			console.error(sub);
			if(sub && judge(sub)){
				res[res.length] = sub;
			}
		}
	}
	return res;
}
console.log(getAllSubs('abaac'));

思路2:

动态规划的方法。开辟一个P[i][j]用来表示str[i..j]是否为回文,P[i][j]的状态转移方程如下:


当i==j时,P[i][j]=true
当i+1==j时,P[i][j]=str[i]==str[j]
其他,P[i][j]=P[i+1][j-1]&&(str[i]==str[j])


这样,这个方法的时间复杂度为O(n^2),空间复杂度为O(n^2)。比暴力法有很大的改进。
代码实现:
// 获取所有的子串回文情况
	function getP2(str){
		var len = str.length,
			gap = '_';
		var p = {};
		var i,j,L;
		// 只有一个字符的情况是回文
		for( i =0;i<len;i++){
			p[i+gap+i] = true;
		}
		// L是i和j之间的间隔数(因为间隔数从小到大渐增,所以大的间隔数总能包含小的间隔数)
		for(L=2;L<=len;L++){
			// 从0开始
			for(i=0;i<=len-L;i++){
				j = i+L-1;
				if(L === 2){
					p[i+gap+j] = (str[i] === str[j]);
				}else{
					p[i+gap+j] = (str[i]===str[j])&&p[i+1+gap+(j-1)];
				}
			}
		}
		return p;
	}

思路3:

可以从上面那个方法的状态转移方程获得启发,对于每一个回文子串可以先确定一个中心,然后向两边扩展,这样可以在时间复杂度O(n^2),空间复杂度O(1)的情况下完成,需要注意的是,长度为奇数和偶数的中心的情况是不同的。
代码实现:
// 方法3
function getP3(str){
	var maxLength = 1,
		start = 0,
		len = str.length,
		low,high;
	for(var i=1;i<len;++i){
		low = i-1;
		high = i;
		while(low>=0 && high < len && str[low] === str[high]){
			if(high-low+1>maxLength){
				start = low;
				maxLength = high-low+1;
			}
			--low;
			++high;
		}
	}
	low = i-1;
	high = i+1;
	while(low>=0 && high < len && str[low] === str[high]){
		if(high-low+1>maxLength){
			start = low;
			maxLength = high-low+1;
		}
		--low;
		++high;
	}
	return maxLength;
}

思路4:

第四个方法采用后缀数组,将最长回文子串的问题转化为最长公共前缀的问题。具体的做法就是:将整个字符串翻转之后,拼接到原字符串后,注意用特殊字符分开,这样问题就变成了新的字符串的某两个后缀的最长公共前缀的问题了。这个方法比较强大,很多字符串的问题都能够巧妙的解决。不过实现起来也相对比较难,好的实现和差的实现时间复杂度相差很大。

后缀数组是一种对字符串处理的比较有力的实现方案,可以用于求解最大公共子串、最长回文串等问题。但是其实现思路还是比较难以理解的。基本的思想就是获取字符串的后缀字符串,然后获得后缀字符串的最长公共前缀。其中牵扯到一系列的表示(如后缀数组、排序数组等),对于如何求得这些表示又有不同的对应方案。具体的论文可参考:后缀数组——处理字符串的有力工具

思路5:

第五个方法叫做Manacher算法,是一种线性时间的方法,非常巧妙。引入一个技巧,可以使得奇数和偶数的情况统一处理。具体做法如下:abba转换为#a#b#b#a#,也就是在每一个字符两边都加上一个特殊字符,这样,不管是奇数长度的字符串还是偶数长度的字符串就都能转化为奇数长度的字符串了。
然后创建一个新的P[i]表示,以第i个字符为中心的回文字串的半径。例如上面的例子,对应的P如下,设S为原始字符串:

S # a # b # b # a #
P 1 2 1 2 5 2 1 2 1
通过观察上面的表,大家可以发现P[i]-1就是实际回文字串的长度。如果知道P,遍历一次就知道最长的回文子串。可以该如何计算P呢?这是这个算法最核心的部分。
具体可见下面的博客: Manacher's ALGORITHM: O(n)时间求字符串的最长回文子串
代码实现:
function getP5(str){
	var p = [], 
		mx = 0,
		id = 0;
	var i;
	// 将字符串转化为奇数长度获取到新的字符串
	var newStr = '#';
	var len = str.length;
	for(i = 0;i<len;i++){
		newStr += str[i]+'#';
	}
	var newLen = newStr.length;
	for(i = 0;i<newLen;i++){
		p[i] = 0;
	}
	// 时间复杂度为O(n),空间复杂度为O(1)获取到所有的子回文的长度值组成的数组
	for (i = 0;i < newLen; i++) {
		// mx表示当前边界值最大的回文子串的边界值
		p[i] = mx > i ? Math.min(p[2*id-i], mx-i) : 1;
		// 超出其半径的位置再做额外判断
		while ((newStr[i + p[i]] == newStr[i - p[i]]) && newStr[i + p[i]]){
			p[i]++;
		}
		// 获取到边界最大的回文子串的中心位置以及边界值,以保证后续迭代可以做以上快捷处理
		if (i + p[i] > mx) {
			id = i;
			mx = id + p[i];
		}
	}
	return p;
}

通过代码实现后,发现上面的博客对于其中的原理的分析并不十分准确。最基本的原则就是2个:
1、如果一个字符串(统一转化为了奇数长度)是回文串,中间字符位置为m。如果其子串S[i,i+x]是回文串,那么其相对于中间字符的对称子串S[2m-i,2m-(i+x)]也一定是回文串;
2、回文串一定是基于中心字符串对称的。也就是:S[i] == S[2m-i]。
然后分析下核心代码:
p[i] = mx > i ? Math.min(p[2*id-i], mx-i) : 1;
		// 超出其半径的位置再做额外判断
		while ((newStr[i + p[i]] == newStr[i - p[i]]) && newStr[i + p[i]]){
			p[i]++;
		}
		// 获取到边界最大的回文子串的中心位置以及边界值,以保证后续迭代可以做以上快捷处理
		if (i + p[i] > mx) {
			id = i;
			mx = id + p[i];
		}

分析后得知,mx并不是像博文中说的表示最大回文串的边界值,mx表示当前边界值最大的回文子串的边界值。由于超出边界值的部分无从判断是否还能与原串组成回文串,所以要额外判断。

注:以上的思路分析都是基于待字闺中的面试题分析,代码实现为自己的实现。

版权声明:本文为博主原创文章,未经博主允许不得转载。

Manacher算法:求解最长回文字符串,时间复杂度为O(N)

回文串定义:“回文串”是一个正读和反读都一样的字符串,比如“level”或者“noon”等等就是回文串。回文子串,顾名思义,即字符串中满足回文性质的子串。 经常有一些题目围绕回文子串进行讨论...
  • yzl_rex
  • yzl_rex
  • 2012年08月26日 08:44
  • 31158

JavaScript判断回文字符串

这篇博文将提供JavaScript判断回文字符串的几种方法。
  • esir82
  • esir82
  • 2016年08月11日 09:10
  • 4092

数据结构实验之求二叉树后序遍历和层次遍历(根据前序中序还原二叉树)

数据结构实验之求二叉树后序遍历和层次遍历 Time Limit: 1000MS Memory limit: 65536K 题目描述  已知一棵二叉树的前序遍历和中序遍历,求二叉树的后序...

HTTPS和HTTP的概念和区别

HTPPS和HTTP的概念 HTTPS(全称:Hypertext Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTT...

【字符串处理算法】回文判断的算法设计及C代码实现

一、需求描述输入一个字符串,编写程序判断这个字符串是否是回文串。为了便于说明,设定输入的字符串分为中文字符串和非中文字符串两种。其中,中文字符串中仅包含中文字符,非中文字符串中不包含中文字符。所谓回文...
  • zhouzxi
  • zhouzxi
  • 2016年02月22日 17:41
  • 2882

#牛客#代码实现:最小编辑代价、最长增长子序列、汉诺塔进阶、单链表相交

#牛客#代码实现:最小编辑代价、最长增长子序列、汉诺塔进阶、单链表相交...

最长回文串——manacher算法java实现

最长回文串是一个很好玩的话题,给出一个无序的不定长的字符序列,如何知道里面的最长回文串呢? manacher算法的思想是 把偶数、奇数长的字符序列变成奇数长度 创建一个与字符串等长的数组,用来记录字符...

三指针法求字符串最长回文

  • 2014年08月17日 22:35
  • 5KB
  • 下载

Java代码实现负载均衡五种算法

前言:       负载均衡是为了解决并发情况下,多个请求访问,把请求通过提前约定好的规则转发给各个server。其中有好几个种经典的算法。在用java代码编写这几种算法之前,先来了解一下负载均衡这个...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:最长回文的五种实现(js代码实现)
举报原因:
原因补充:

(最多只允许输入30个字)