5.Longest Palindromic Substring

来源:互联网 发布:苹果young网络客户端 编辑:程序博客网 时间:2024/05/17 02:31

Given a string S, find the longest palindromic substring in S. You may assume that the maximum length of S is 1000, and there exists one unique longest palindromic substring.
最长回文子字符串:

1.思路一:暴力枚举,以每个元素为中间元素,同时从左右出发,复杂度O(n2)
2.思路二:动规,复杂度O(n2)。设状态为{f(i,j)},表示区间[i,j]是否为回文串,则状态转移方程为
这里写图片描述
3.思路三:Manacher’s Algorithm, 复杂度O(n)。详细解释见 {http://leetcode.com/2011/11/longest-palindromic-substring-part-ii.html} 。

// LeetCode, Longest Palindromic Substring// 动规,时间复杂度O(n^2),空间复杂度O(n^2)class Solution {public:    string longestPalindrome(string s) {        const int n = s.size();        bool f[n][n];        fill_n(&f[0][0], n * n, false);        // 用 vector 会超时        //vector<vector<bool> > f(n, vector<bool>(n, false));        size_t max_len = 1, start = 0;  // 最长回文子串的长度,起点        for (size_t i = 0; i < s.size(); i++) {            f[i][i] = true;            for (size_t j = 0; j < i; j++) {  // [j, i]                f[j][i] = (s[j] == s[i] && (i - j < 2 || f[j + 1][i - 1]));                if (f[j][i] && max_len < (i - j + 1)) {                    max_len = i - j + 1;                    start = j;                }            }        }        return s.substr(start, max_len);    }};

首先我们把字符串S改造一下变成T,改造方法是:在S的每个字符之间和S首尾都插入一个”#”。这样做的理由你很快就会知道。

例如,S=”abaaba”,那么T=”#a#b#a#a#b#a#”。

i = 0 1 2 3 4 5 6 7 8 9 A B C

T = # a # b # a # a # b # a #

P = 0 1 0 3 0 1 6 1 0 3 0 1 0
i为索引,P是回文长度。
显然最长子串就是以P[6]为中心的”abaaba”。

现在,想象你在”abaaba”中心画一道竖线,你是否注意到数组P围绕此竖线是中心对称的?再试试”aba”的中心,P围绕此中心也是对称的。这当然不是巧合,而是在某个条件下的必然规律。我们将利用此规律减少对数组P中某些元素的重复计算。

我们来看一个重叠得更典型的例子,即S=”babcbabcbaccba”。
这里写图片描述
上图展示了把S转换为T的样子。假设你已经算出了一部分P。竖实线表示回文”abcbabcba”的中心C,两个虚实线表示其左右边界L和R。你下一步要计算P[i],i围绕C的对称点是i’。你有办法高效地计算P[i]吗?

我们先看一下i围绕C的对称点i’(此时i’=9)。
这里写图片描述
据上图所示,很明显P[i]=P[i’]=1。这是因为i和i’围绕C对称。同理,P[12]=P[10]=0,P[14]=P[8]=0
这里写图片描述
现在再看i=15处。此时P[15]=P[7]=7?错了,你逐个字符检测一下会发现此时P[15]应该是5。

为什么此时规则变了?
这里写图片描述
如上图所示,两条绿色实线划定的范围必定是对称的,两条绿色虚线划定的范围必定也是对称的。此时请注意P[i’]=7,超过了左边界L。超出的部分就不对称了。此时我们只知道P[i]>=5,(因为i`超过5长度的地方位于上图红色实线处,所以已经超出了判定的范围。)至于P[i]还能否扩展,只有通过逐个字符检测才能判定了。

在此例中,P[21]≠P[9],所以P[i]=P[15]=5。

我们总结一下上述分析过程,就是这个算法的关键部分了。

if P[ i' ] < Ri,then P[ i ]P[ i' ]else P[ i ]R - i. (此时要穿过R逐个字符判定P[i]).

很明显C的位置也是需要移动的,这个很容易:

如果i处的回文超过了R,那么就C=i,同时相应改变L和R即可。

每次求P[i],都有两种可能。如果P[i‘] < R – i,我们就P[i] = P[i’]。否则,就从R开始逐个字符求P[i],并更新C及其R。此时扩展R(逐个字符求P[i])最多用N步,而求每个C也总共需要N步。所以时间复杂度是2*N,即O(N)。

// LeetCode, Longest Palindromic Substring// Manacher’s Algorithm// 时间复杂度O(n),空间复杂度O(n)class Solution {public:    // Transform S into T.    // For example, S = "abba", T = "^#a#b#b#a#$".    // ^ and $ signs are sentinels appended to each end to avoid bounds checking    string preProcess(string s) {        int n = s.length();        if (n == 0) return "^$";        string ret = "^";        for (int i = 0; i < n; i++) ret += "#" + s.substr(i, 1);        ret += "#$";        return ret;    }    string longestPalindrome(string s) {        string T = preProcess(s);        const int n = T.length();        // 以T[i]为中心,向左/右扩张的长度,不包含T[i]自己,        // 因此 P[i]是源字符串中回文串的长度        int P[n];        int C = 0, R = 0;        for (int i = 1; i < n - 1; i++) {            int i_mirror = 2 * C - i; // equals to i' = C - (i-C)            P[i] = (R > i) ? min(R - i, P[i_mirror]) : 0;            // Attempt to expand palindrome centered at i            while (T[i + 1 + P[i]] == T[i - 1 - P[i]])                P[i]++;            // If palindrome centered at i expand past R,            // adjust center based on expanded palindrome.            if (i + P[i] > R) {                C = i;                R = i + P[i];            }        }        // Find the maximum element in P.        int max_len = 0;        int center_index = 0;        for (int i = 1; i < n - 1; i++) {            if (P[i] > max_len) {                max_len = P[i];                center_index = i;            }        }        return s.substr((center_index - 1 - max_len) / 2, max_len);      }};

在程序中,在开头和结尾加了^和$,这使得最后算index的时候所有index向后移动了一位,所以最后除以2了。s.substr((center_index - 1 - max_len) / 2, max_len); 例如图片上11的index其实在给程序中为12。

int i_mirror = 2 * C - i; // equals to i' = C - (i-C)P[i] = (R > i) ? min(R - i, P[i_mirror]) : 0;

i的镜像 i’ = 中心点C - (i-C)。
在i位于R的左侧时,判断此时P[i`]的长度是否达到了红色实线,如果达到了,那只能够取距离红色实线的长度(即目前能够判断的回文长度(加入了#,所以此长度即为回文长度。),是否还能更长只能稍后逐个判断。)

while (T[i + 1 + P[i]] == T[i - 1 - P[i]])      P[i]++;

判断红色实线的数字是否属于回文字符串的一部分,如果是,则将回文长度加一,不是,则退出循环。

if (i + P[i] > R) {     C = i;     R = i + P[i];}

然后更新中心点和R的边界。

1 0
原创粉丝点击