Leetcode日志--LongestPalindromicSubstring

来源:互联网 发布:淘宝代运营合作 编辑:程序博客网 时间:2024/05/19 23:24

不得不说这是一道很经典的处理字符串的算法题目,首先给出题目:

Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.

给定一串字符,找到这串字符中最长的回文子串,你可以假定字符串s的最大长度是1000。回文串,也就是正着读反着读都是相同的字符串,比如说:abba,weighgiew等。

切入正题:

这道题一开始我的思路是把串反过来再存一遍,然后比较找到两个串的最大共同部分。但是后来发现这种想法是错误的,因为存在这样一种情况:

原串和反串只是字符顺序相同,但并不回文,例如:weighbphgiew,反过来就是weighpbhgiew,最大相同部分是weigh和hgiew,但它显然不是我们想要的最大回文字串。后来看了题目的Solution后发现对这个也有解决方案:

 To rectify this, each time we find a longest common substring candidate, we check if the substring’s indices are the same as the reversed substring’s original indices. If it is, then we attempt to update the longest palindrome found so far; if not, we skip this and find the next candidate.

 也就是每当我们找到一个最大回文子串,我们只需要检查这个这个子串的索引是不是和这个子串反过来之后的索引一样,如果一样的话就认定它是一个最大回文子串并且更新,如果不是就跳过继续查找。

说了这么多,其实这道题的标准解法早已经有了,也就是 Manacher's Algorithm,俗称马拉车算法。 这个算法的时间复杂度只有O(n),相比其他算法而言效率非常之高。

首先是准备工作,

(1)把给定字符串s的每个字符前后都加上一个符号,比如: #a#b#b#a#。这样的话就可以保证无论给定的字符串的字符个数是单数还是双数,最后得到的字符串t的字符个数都是单数。

 (2)为了防止第一次搜索时越界,还要再在字符串的前面加上一个字符,例如:*#a#b#b#a#。(这个看到后面就自然明白了),之所以不再后面也加一个是因为字符串默认最后有一个'/0'。

 (3)设两个变量,id和mx。id为最大回文子串中心的位置,mx是最大回文串能延伸到的最右端的位置,另设一个数组p[],大小等于t.length()。也就是mx=id+p[i]。这个数组中的每一个元素存储的都是以i索引为中心,向两端按回文条件搜索之后得到的最大的右端点与id的差值+1,所以p[i]*2-1就是修饰过的回文子串的长度,而p[i]-1就是原始字符串中回文子串的长度。

举个例子,*#a#b#b#a# 拿这个来说,p[1]=1,p[2]=2,p[3]=1,p[4]=2,p[5]=5,p[6]=2,p[7]=1,p[8]=2,p[9]=1;,所以最大的回文子串长度就是p[5]-1,也就是4。因此,我们只需要找出每一个p[i]的值,就可以确定最大回文子串的长度和位置。

但事情并没有这么简单,要确保时间复杂度为O(n),必须保证每个子串只搜索一次,所以还要有相应措施。其实可以分为这几种情况。

①如果mx>i,且mx-i > p[2*id-1]的话,那么p[i]的初值就不是1,而是p[2*id-1]。这个画个图很容易看出来。

 

现在id是已搜索过的最长的回文子串的中心位置,mx和my是它的两个极限位置,如果mx>i,也就是说现在以i为中心的回文子串处在id子串的内部,因此找到i关于id对称的位置j,也就是2*id-1的位置,以这个这个位置为中心的最大回文子串是前面已经搜索过的,而且当mx-i>p[2*id-1]时,必定与i位置处的子串相同,所以此时p[i]初值为p[2*id-1]。

②如果mx>i,且mx-i<p[2*id-1]。 p[i]的初值应为mx-i,也就是下图所示的情况。


由于mx之后的字符未知,所以要从mx开始一个个的搜索,因此p[i]初值设为mx-i。

③如果mx<i,那么p[i]只能为1,开始一个个的搜索。


总结成一行代码就是:p[i] = mx>i ? min(p[2*id-1],mx-i) :1;

只要找到每个p[i]的值,就可以找到最大子串所在的位置和其长度,前面已经介绍过,p[i]-1这个值就是源字符串中的回文子串的长度。初始位置可以通过(id-p[i]+1)/2得到。


下面是完整代码:

class Solution {public:    string Manacher(string s) {    string t = "*#";    for (int i = 0; i < (signed)s.size(); ++i) {        t += s[i];        t += "#";    }    vector<int> p(t.size(), 0);    int mx = 0, id = 0, resLen = 0, resCenter = 0;    for (int i = 1; i < (signed)t.size(); ++i) {        p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;        while (t[i + p[i]] == t[i - p[i]]) ++p[i];        if (mx < i + p[i]) {            mx = i + p[i];            id = i;        }        if (resLen < p[i]) {            resLen = p[i];            resCenter = i;        }    }    return s.substr((resCenter - resLen + 1) / 2, resLen - 1);}};

如有纰漏之处敬请指教。


原创粉丝点击