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 };
posted on 2014-09-14 12:33 laihaiteng 阅读(145) 评论(0) 编辑 收藏 举报