最长回文子串

最长回文子串

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

字符串如“abcba”,”abbbba”这样呈中心对称的子串称为回文串。

该题目是一个老题了。有多种不同的解法。我整理一下方便以后查询。


暴力动态规划法

这种方法是我们看到这个题目后最easy想到的方法,暴力搜索全部的子串。推断每一个子串是否是回文串。我们用一个二维空间记录已计算过的子串是否为回文串,这样之后针对每一个新子串进行判定的时候能够利用之前记录信息辅助推断。主要的动规方程例如以下:

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

该方法的代码非常easy,可去网上查询,终于的时间复杂度是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)。


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

【推广】 免费学中医,健康全家人
原文地址:https://www.cnblogs.com/zhchoutai/p/8384633.html