Manacher‘s Algorithm-马拉车算法-JavaScript实现

马拉车算法简介

  马拉车算法:Manacher‘s Algorithm是用来查找一个字符串的最长回文子串的线性方法,由一个叫Manacher的人在1975年发明的,这个方法的最大贡献是在于将时间复杂度提升到了线性,时间复杂度为O(n)

什么是最长回文子串?

  首先说什么是回文字符串:从左往右读和从右往左读是一样的就是回文字符串,比如"bob"是奇数形式的回文,"noon"就是偶数形式的回文,奇偶指的是字符串的长度。
  这样就简单了:最长回文子串就是一个字符串中的长度最大的那个回文字符串。

如何处理字符串长度奇偶个数呢?

  上面一个小点说到了字符串长度分奇偶,如果在分析字符串时分为奇数和偶数分别讨论,是不是感觉有点不好啊!我们要的是统一起来处理,只有一种情况。
  马拉车算法提供的思路是对字符串做一个预处理:在每个字符的前后加上一个符号,比如“#”。来看看处理后的字符串是什么样的:
  bob   -->  #b#o#b#
  noon   -->  #n#o#o#n#
  分析上面两个字符串:长度为奇数的字符串处理后还是奇数个;长度为偶数的字符串处理后变为了奇数个。现在两种情况已经统一了:统一对奇数个的字符串做分析。

算法分析

  马拉车算法在暂时已知的第 i 个字符之前已经推出来的最长回文长度下,要么直接得出第 i 个字符的回文长度,要么拓展匹配,效率是很高的,关键就是下面的这句代码:

  child[i] = i<right ? Math.min(child[2*mid-i],right-i) : 1;

  首先判断第 i 个字符是否还在右边界内:右边界就是第 i 个字符加上它的回文长度 child[i]
  1、如果不在右边界内:那么没有办法走捷径直接得出当前字符的回文长度,child[i]赋值为 1 ,接下来还是老老实实地一个字符一个字符匹配,体现在 while 循环语句。
  2、如果在右边界内:那么可以走捷径了,根据回文特性,在暂时已知的最长回文子串的中心 mid 左右两边肯定是对称的,所以 i 关于 mid 的对称点是:mid-(i-mid),简化后就是 2mid-i ;根据对称性:child[i] 在右边界内的那一部分肯定等于 child[2mid-i] 在左边界内的那一部分。到底取什么值就是 Math.min 来判断的。
  Math.min(child[2*mid-i],right-i) 解释:这里为什么选两者中小的那个呢?
  1)、当 child[2*mid-i] 较小时,说明以 i 点处字符为中心的回文完全在(mid,right)区间内,直接附值即可。
  2)、当 child[2*mid-i] 较大时,说明以 i 点处字符为中心的回文至少在(mid,right)区间内,可能有超出右边界的却又能够匹配的字符,所以先附值 right-i,然后再对超出 right 边界的字符一一做对称匹配。接下来的匹配体现在 while 循环语句。

贴上JavaScript代码

  String.prototype.longestPalindrome = function()
  {
	    var str = "#",	//预处理字符串
	        mid = 0,  	//当前最长回文子串的中心
	        right = 0,	//当前最长回文子串的右边界
	        maxLen = 0,	//最长回文子串长度
	        maxLenMid = 0,//最长回文子串的中心
	        child = [];	  //存放每个字符的回文长度
	    /*
	      预处理字符串,之后的字符串长度奇偶个数就统一了
	      比如  abba   -->  #a#b#b#a#     偶数个长度变为了奇数个
	            abfba  -->  #a#b#f#b#a#   奇数个长度没有变
	    */
	    for(var i=0;i<this.length;i++) {
	      str += this[i] + '#';
	    }
	    for(var i=0;i<str.length;i++) 
	    {
	      /*第 i 个字符是否还在右边界内
	        Math.min(child[2*mid-i],right-i) 解释:这里为什么选两者中小的那个呢?
	        1、当child[2*mid-i]较小时,说明以 i 点处字符为中心的回文*完全*在(mid,right)区间内,直接附值即可。
	        2、当child[2*mid-i]较大时,说明以 i 点处字符为中心的回文*至少*在(mid,right)区间内,可能有
	           超出的能够匹配的字符,所以先附值 right-i,然后再对超出 right 边界的字符一一做对称匹配。 
	        算出 child[i] 的值了,下面的 while 循环就是对接下来的字符做匹配操作的
	      */
	      child[i] = i<right ? Math.min(child[2*mid-i],right-i) : 1;
	      //接下来从 i+-child[i] 再做逐个比较
	      while (str.charAt(i+child[i]) == str.charAt(i-child[i])){
	        child[i]++;
	      }
	      //是否更新右边界:如果当前 i 加上 i 的回文长度 child[i] 大于原先的右边界,就更新
	      if(right < child[i]+i) {
	        mid = i;
	        right = child[i]+i;
	      }
	      //是否更新最长回文子串的长度及其中心位置:如果当前 i 的回文长度child[i]大于原先的最长回文长度,就更新
	      if(maxLen < child[i]) {
	        maxLen = child[i];
	        maxLenMid = i;
	      }
	    }
	    //根据两个变量 maxLenMid 和 maxLen 判断子串在原字符串中的位置,取出子串再返回
	    return this.substr((maxLenMid+1-maxLen)/2,maxLen-1);
  };
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
算法是一种常用于字符串匹配的算法,其核心思想是利用回文串的对称性来减少比较次数。Java中可以通过以下步骤实现算法: 1. 预处理字符串,将字符串中的每个字符用一个特殊字符隔开,如:将字符串"abc"变成"#a#b#c#" 2. 维护一个数组P,其中P[i]表示以i为中心的最长回文子串的半径长度。具体实现时,可以使用一个中心点center和右边界right来维护,其中center表示当前已知的最长回文子串的中心点,right表示该回文子串的右边界。根据回文串的对称性,可以利用已知回文串的左侧字符的对称点来推出右侧字符的回文半径。 3. 遍历字符串,根据P数组更新center和right,并记录最长回文子串的起始位置和长度。 以下是Java代码实现示例: ```java public class ManacherAlgorithm { public static String longestPalindrome(String s) { if (s == null || s.length() == 0) { return ""; } StringBuilder sb = new StringBuilder(); sb.append("#"); for (int i = 0; i < s.length(); i++) { sb.append(s.charAt(i)); sb.append("#"); } String str = sb.toString(); int[] P = new int[str.length()]; int center = 0, right = 0; int start = 0, maxLen = 0; for (int i = 0; i < str.length(); i++) { if (i < right) { P[i] = Math.min(right - i, P[2 * center - i]); } while (i - P[i] - 1 >= 0 && i + P[i] + 1 < str.length() && str.charAt(i - P[i] - 1) == str.charAt(i + P[i] + 1)) { P[i]++; } if (i + P[i] > right) { center = i; right = i + P[i]; } if (P[i] > maxLen) { start = (i - P[i]) / 2; maxLen = P[i]; } } return s.substring(start, start + maxLen); } public static void main(String[] args) { String s = "babad"; System.out.println(longestPalindrome(s)); } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值