最长回文子串

来源:互联网 发布:1000base x sfp端口 编辑:程序博客网 时间:2024/06/03 23:45

最长回文子串

题意:给定一个字符串s,找出该字符串中最长的回文子串。

字符串如“abcba”,”abbbba”这样呈中心对称的子串称为回文串。该题目是一个老题了,有多种不同的解法,我整理一下方便以后查询。


暴力动态规划法

这个方法是我们看到这个题目后最容易想到的方法,暴力搜索所有的子串,判断每个子串是否是回文串;我们用一个二维空间记录已计算过的子串是否为回文串,这样之后针对每个新子串进行判定的时候可以利用之前记录信息辅助判断。基本的动规方程如下:

  • s[i]==s[j] 时 : dp[i][j] = dp[i+1][j-1]; (j>0)
  • s[i] != s[j] 时: dp[i][j] = false ;

该方法的代码很简单,可去网上查询,最终的时间复杂度是O(n^2).

中心点法

对于回文子串,我们还需要想到其性质:字符串关于中心位置对称。那么可以看到若子串s[3,5]不是回文串的话,那么s[2,6],s[1,7]等必然不是回文串,利用该性质我们可以省去一些不必要的比较计算。
相对于第一种方法,肯定有着更好的实际运行效果。
参考代码如下:

 string longestPalindrome(string s) {        if (s.empty()) return "";        if (s.size() == 1) return s;        int min_start = 0, max_len = 1;        for (int i = 0; i < s.size();)         {              if (s.size() - i <= max_len / 2) break;//不会超过最大回文串长度,直接跳出              int j = i, k = i;              while (k < s.size()-1 && s[k+1] == s[k]) ++k; // 跳过重复的元素.              i = k+1;              while (k < s.size()-1 && j > 0 && s[k + 1] == s[j - 1]) { ++k; --j; } // 向左右两边扩张.              int new_len = k - j + 1;              if (new_len > max_len) { min_start = j; max_len = new_len; }        }        return s.substr(min_start, max_len); }

思路即是 从字符串的第一个字符做中心点开始,不断向串的两边扩张直到不能扩张,之后将中心点右移,不断重复操作;过程中记录下回文串起始点和回文长度。

当然该方法的最坏的时间情况仍然是O(n^2). 对于s=”aaaaaaaa”这样的特殊字符串,每个中心点都会左右扩张到字符串的边缘,因此此时时间复杂度为O(n^2).

Manacher算法

该算法从网上学来,充分巧妙的利用了回文串的性质,能保证线性的时间复杂度。

算法思路:

  • 首先在每相邻两个字符间插入一个分隔符(这个分隔符要在原串中没有出现过),一般都用‘#’分隔,这样就将奇数长度与偶数长度回文串统一起来考虑了(回文串长度全为奇数了),然后用一个辅助数组P记录以每个字符为中心的最长回文串的半径。为了方便理解,可以参照下图:
    这里写图片描述

  • 从新字符串的开始字符为中心点,不断向串的两边扩张,直到不能扩张,之后将中心点右移,不断重复操作;

  • 算法中要维护一个中心点(center)和一个边界(boundary).从新的字符串的首字符开始,不断向两边扩张,直到达到以该中心点构成的最长回文子串,此时若该回文串的右边界大于boundary,那就更新boundary和center。也就是说,boundary记录算法运行至今的最偏右的右边界…

    • 每次遍历到一个新的中心点时,我们可以判断该点是否落在boundary的左边,若落在左边,那么则可以利用P数组简化计算,如图(图丑不要在意细节)。

    这里写图片描述

    • 根据回文串的对称性,我们找到i关于center的对称点j,若P[j]小于boundary-i 那么为了保证大的回文串的对称性,此时P[i] =P[j],没有必要继续比较下去。

    这里写图片描述

    • 若P[j]>boundary-i 那么还需要继续计算P[i]。让P[i] = bounar-i,之后继续循环判断 (t[i+1+P[i]] == t[i-1-P[i]])。
  • 算法过程中 同时记录最大回文子串的长度和起始点。

参考代码如下:

string longestPalindrome(string s) {        string t;    for(int i=0;i<s.size();i++)        t=t+"#"+s[i];    t.push_back('#');    vector<int> P(t.size(),0);    int center=0,boundary=0,maxLen=0,resCenter=0;    for(int i=1;i<t.size()-1;i++)    {        int j= 2*center -i;        if(boundary>i)         {            P[i] = min(P[j],boundary-i);        }else        P[i] =0;        while(i-1-P[i]>=0 && i+1+P[i]<t.size() && t[i+1+P[i]] ==  t[i-1-P[i]])            P[i]++;        if(i+P[i]>boundary)         { // update center and boundary                center = i;                boundary = i+P[i];        }        if(P[i]>maxLen)         { // update result                maxLen = P[i];                resCenter = i;        }     }    return s.substr((resCenter - maxLen)/2, maxLen);}

时间复杂度分析:boundary在整个算法过程中是一直向右移动的,并且每次移动都是因为新的对称字符的比较,通过聚合分析方法来考虑代码段

while(i-1-P[i]>=0 && i+1+P[i]<t.size() && t[i+1+P[i]] ==  t[i-1-P[i]])            P[i]++;

这里的代码执行 引起了boundary的向右扩张,而boundary最大只能是n。所以整个算法的时间复杂度是线性的,即O(n)。


本文分析了三种不同的求解最长回文子串的方法,希望能给读本文的人一点收获和思考。

1 0
原创粉丝点击