Manacher马拉车算法求最长回文子串

终于把马拉车算法搞明白了!赶紧记录一下。

这个算法用于查找一个字符串的最长回文子串

马拉车算法依次给数组p[i]赋值,马拉车算法的本质就是在每次给数组p[i] 赋值时尝试进行偷懒
例如,当要给p[6]赋值时,前面分别以 p[0],p[1],p[2],p[3],p[4],p[5]为中心的回文子串都已经找出来了,
而且这六个回文子串中的   最长的回文子串  和  最靠近右端的回文子串  也找出来了。

如果这个最靠近右端的回文子串特别长的话(怎么个长法,这是有条件的,看后文),

在找以p[6]为中心的回文子串时就可以偷懒了!哈哈!

一个小插曲,讲个小故事。要结合我下面的两个条件1和2哦!
说有一个洋葱,里面的某一层有一个虫子。要找到这个虫子就得一层一层的找。我们知道剥洋葱是从外面往里面一层一层的剥,
但是这里找虫子恰恰相反,必须从里面往外面一层一层的找。你明白了吗?
条件1是说 不好意思,咱俩的洋葱没有啥关系,所以你就得老老实实的从最里面的一层开始往外找了。

条件2是说 咱俩的洋葱m层以内完全相同,已经知道我的洋葱n层以内都没有虫子,

由此可以确定你的洋葱min{m, n}以内都没有虫子,

所以你找的时候只需要从min{m, n}+1层往外找就行了。

看看,马拉车算法偷懒就在这个地方。

插曲讲完了,接着说。


1.如果这个最靠近右端的回文子串的右端点mx在i的左边,那不好意思,你前面找出来的回文子串再多,
对于我现在要找的这个以i为中心的回文子串来说没有任何帮助,但是考虑到回文子串再短也至少是一个字符吧,那就先为你赋值为1。
所以后面的while()循环语句就得老老实实的从你字符自身两边的两个字符开始向外层依次判断是否相同,然后找出这个回文子串


2.如果这个最靠近右端的回文子串的右端点mx在i的右边,嘻嘻!看来有戏!
你前面千辛万苦找出来的那个最靠近右端的回文子串对于我现在要找的这个以i为中心的回文子串来说就有帮助。

还需要借助上面的小插曲 !
 

说 你有一个洋葱,我有一个洋葱,咱俩的洋葱都是有10层而且5层及以内完全相同,什么意思,就是5层及以内,你的洋葱在第几层有虫子,

我的洋葱在第几层也有虫子,就是说5层及以内要有虫子都有,而且位置一样,要没有虫子都没有。

2.1 如果你的洋葱3层及以内没有虫子,那我的洋葱也是3层及以内没有虫子,

   p[2 * id - i] = 3
   mx - i = 5

   p[i] 只能取两者的最小值3  故p[i] = p[2 * id - i]

因为你的洋葱4层和5层有没有虫子不确定,那我的洋葱4层和5层也不确定,所以后面我要从第4层开始往外层找虫子。

2.2 如果你的洋葱7层及以内没有虫子,那我的洋葱5层及以内没有虫子,因为你的洋葱6层和7层和我的不一样,那我的洋葱6层和7层更加不确定了,

   p[2 * id - i] = 7
   mx - i = 5

   p[i] 只能取两者的最小值5  故p[i] = mx - i

但是咱俩5层及以内是相同的, 所以后面我要从第6层开始往外层找虫子。

#include <windows.h> 
#include <vector>
#include <string>
#include <iostream>
using namespace std;

string Manacher(string s) 
{
    // Insert '#'
    string t = "$#";
    for (int i = 0; i < s.size(); ++i) 
    {
        t += s[i];
        t += "#";
    }
   
    // Process t
    vector<int> p(t.size(), 0);
    int id = 0;//记录最靠近右端的回文子串的中心点 
    int mx = 0;//记录最靠近右端的回文子串的右端点 
    int resCenter = 0;//记录所有回文子串中最长回文子串的中心点 
    int resLen = 0;//记录所有回文子串中最长回文子串的长度 
    for (int i = 1; i < t.size(); ++i) 
    {
		//p[i]获得的值越大越好,p[i]的值越大,while()函数遍历的时候不必从1开始,而是从更大的p[i]开始
		//这样大大缩短的while()的执行次数,这也是马拉车算法的核心所在
        p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;
      
		//求以i为中心点的最大回文串 
        while (t[i + p[i]] == t[i - p[i]]) 
			++p[i];

		//下面这个if()语句的作用是找出最靠近右端的回文子串
		//如果 当前找到的这个以i为中心点的回文子串 比 上一个记录的最靠右端的回文子串还要靠近右端
		//就把这个回文子串 当做 最靠近右端的回文子串 
        if (mx < i + p[i]) 
		{
	    	id = i;
            mx = i + p[i];
        }
        
        //id 和 mx 记录的是最靠近右端的回文子串的中心点和右端点,但是这个回文子串不一定是最长的回文子串
		//所以用 resCenter 和 resLen 记录目前得到的最长的回文子串
		//遍历到最后记录的就是要求的最长的回文子串 
        if (resLen < p[i]) 
		{
            resLen = p[i];
            resCenter = i;
        }
    }
    
    //根据处理后的最长回文子串的中心点和长度找到这个最长回文子串在原来字符串中的位置,然后输出 
    return s.substr((resCenter - resLen) / 2, resLen - 1); 
}

int main() {
    string s1 = "edcbabcdemmnmm";
    cout << Manacher(s1) << endl;
    system("pause");
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值