leetcode - Longest Palindromic Substring

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),再判断该子串是否为回文串O(n),并记录下最长的那个回文子串,总时间复杂度为O(n3),这么做会超时

2,使用manacher算法,这个算法非常巧妙,时间复杂度和空间复杂度均为O(n),这里分享一个讲解该算法的文章:http://blog.csdn.net/ggggiqnypgjg/article/details/6645824,接下来说说我对这个算法的理解:

  1,首先要明白,这个算法是在枚举中心点算法的基础上优化的,枚举中心点算法就是以每个字符为回文串的中心点,向左右两边扩张,以此获得一个最长的回文串,但要注意,该算法需要考虑奇数和偶数的情况,如"1221"和"121"

  2,在用这个算法计算前,需要对原串s做一个处理,用一个例子来说明好了,如串s = "12212321",处理之后变为串t = "^#1#2#2#1#2#3#2#1#$",之后便是对t进行操作,这么处理有两个原因:第一个原因是在字符之间插入'#'字符,用于统一考虑奇数和偶数的情况,如果回文子串是偶数的,则会以'#'字符为中心点,如果为奇数,则会以原串中的字符为中心点;第二个原因是在头部和尾部插入两个不同的特殊字符,避免处理越界的情况。

  3,设置一个数组P,用于记录以每个字符为中心点时的回文半径,即向右或者向左扩张的长度,这个长度不包括中心点,因此,该长度就是回文子串包含的非#字符的个数,也即有效的字符个数,比如,现在要计算P[5](t的起始字符从0开始),很明显,以t[5]为中心点的回文串是"#1#2#2#1#",向右扩张4个字符,则P[5] = 4,同时也等于该回文串实际的有效字符(1,2,2,1)的个数

  4,好了,要进入该算法的正题了,如果只是枚举中心点的算法,则每次P[i]都是从0开始递增,但在这里,我们会利用回文的性质,来加速这个过程,当我们要计算P[i]时,如果t[i]落在t[j]的回文半径中,则有很好的一个性质,看下图:

先不用管3副图之间的差别,就看i,j,k这3个下标好了,i落在j的回文半径里,k是i相对于j的对称点,其实,回文串就是基于中心字符对称的,抓住这个性质,我们先抛出结论,P[i] >= min(P[k], j + P[j] - i),如何得出这个结论的呢?这就要分析上图的3中情况了

  第1种情况,k的回文范围还在j的回文范围中(除掉k-P[k] == j - P[j]的情况),此时根据j回文串的对称性和k回文串的对称性,可以得出P[i] >= P[k],那i回文串还能否往外扩张呢?假设可以,则根据i回文串和k回文串的对称性可以知道k回文串的范围就不只是此时的长度了,得出矛盾,所以P[i] = P[k]

  第2种情况,k的回文范围超出了j的回文范围,超出的那部分不在j回文串的保证范围内,因此P[i] >= j + P[j] - i,因为j + P[j] - i这段范围是得到保证的最大范围,那i回文串还能否往外扩张呢?假设可以,则根据i回文串和k回文串可以知道j回文串的范围就不只是此时的长度了,得出矛盾,所以P[i] = j + P[j] - i

  第3种情况,k的回文范围刚好在j的回文边界上,根据1的部分推论,有P[i] >= P[k],但由于此时k的回文边界与j的回文边界重合,i是可能往外扩张的,因为此时往外扩张不受到j回文串的制约了,这时需要在P[i] = P[k]的基础上,继续向外扩张来增长i回文串的长度

  当然,上面讨论的情况都是i落在了某个中心点j的回文范围内,如果i不在任何中心点的回文范围内,则必须从P[i] = 0开始,不断试探扩张来计算回文长度

  5,最后,需要把在t串中计算的长度和子串转成原串s中的长度和子串,这个比较容易,就不说了

代码:

 1 #include<string>
 2 #include<vector>
 3 
 4 using namespace std;
 5 
 6 class Solution {
 7 public:
 8     string longestPalindrome(string s) {
 9         if (s.empty())
10         {
11             return s;
12         }
13 
14         string T = preProcess(s);
15         const int length = T.length();
16         vector<int> P; //每个字符为中心点时的回文半径,不包括中心点自己,因此,回文半径即为该回文串实际包含的字符数
17         P.resize(length);
18         P[1] = 0;
19         int C = 1, E = 1; //向前覆盖最大的回文串中心点下标和边界点下标
20         int maxC = 1; //最大回文半径的中心点下标
21 
22         for (int i = 2; i < length - 2; ++i)
23         {
24             P[i] = i < E ? min(P[2 * C - i], E - i) : 0;
25 
26             while (T[i + P[i] + 1] == T[i - P[i] - 1])
27             {
28                 ++P[i];
29             }
30 
31             if (i + P[i] > E)
32             {
33                 C = i;
34                 E = i + P[i];
35             }
36 
37             if (P[i] > P[maxC])
38             {
39                 maxC = i;
40             }
41         }
42         
43         return s.substr((maxC - P[maxC] - 1) / 2, P[maxC]);
44     }
45 
46     string preProcess(string s)
47     {
48         string t = "^#";
49         int length = s.length();
50 
51         for (int i = 0; i < length; ++i)
52         {
53             t.insert(t.end(), s[i]);
54             t.insert(t.end(), '#');
55         }
56         t.insert(t.end(), '$');
57 
58         return t;
59     }
60 };
View Code

 

posted on 2014-09-14 12:33  laihaiteng  阅读(145)  评论(0编辑  收藏  举报

导航