问题描述
问题要求我们找到一个字符串s中最长的回文子串
首先我们看看什么叫做回文,我认为,就是对称,也可以这样理解,将回文字符串从中间折叠,能够刚好吻合。
那么该如何判断回文?有一个方法是递归,一个回文字符串去掉一头一尾,依旧是回文字符串。根据这个原理我们可以把要判断的字符串减去头尾继续递归。
这个用编程语言很好写,就是算法复杂度可能会高一点。不过这次我用的是另外一种方法:找到一个中心点,然后以此为圆心向两边散开(若两边对应位置的值相等,则是回文的)。
为什么我要选择这个方法,原因如下:
这个方法复杂度相对较小
题目要求找最长回文子串,用递归要先获取一个字符串,而我是用的方法是从只含一个元素慢慢成长起来的,更容易找到最长回文子串
要获取最长回文,那么给定字符串的每个元素都要被遍历到,对于递归算法,要提取包含所遍历的元素的所有子串来进行判断,很复杂。本方法虽然也要遍历给定字符串,但每个元素都只需向外扩张一次,易于实现。
知道如何判断回文后就该思考如何解决这个问题。
字符串的长度可以是奇数或偶数,对于奇数长度,如“babad”,按照我选择的判断回文算法,很容易就可以找出最长回文。但是对于偶数长度呢,如”cbbd”:
- 第一次:”c”作为圆心无法向两边扩展
- 第二次:”b”作为圆心,两边”c”和”b”不相等,不是回文
- 第三次:”b”作为圆心,两边”b”和”d”不相等,不是回文
- 第四次:”d”作为圆心无法向两边扩展
这就造成了这个字符串没有回文的假象,实际上”bb”就是这个例子的答案,但是我们做判断的时候跳过了它。
那么如何解决呢,很简单,把所有字符串的长度都变成奇数。我在网上查到了一个Manacher算法:把字符串中相邻字母之间加上一个“”号,这样就会把字符串长度变成奇数。如 “babad”就变成了 “b*a*b*a*d”,同理, “cbbd”也变成了 “c*b*b*d”。
那么解决这个问题的思路已经很清楚了,
- 第一步:对于给定字符串长度超过1000的不予理会,对于长度为1的,直接返回其本身
- 第二步:对给定字符串进行长度变奇数操作,这里需要一个新的字符串temp来记录改变后的字符串
- 第三步:按照之前提到的判断回文的方法,从temp第二个位置开始遍历(第一个位置没有左元素,不可能有回问),到倒数第二个结束(最后一个同理,不可能有回文),当然,遇到星号元素也要判断回文,比如字符串 “b*b”。这里我们需要一个数组来记录遍历的每个元素所能到达的最大回文半径,假设原来字符串长度为n,那么加的 “*”号个数为n-1,抛弃两头,这个数组长度应为2*n - 3。那么记录长度的代码就很好理解了
值得注意
在记录最长回文半径和中心点所在位置的时候,和当前位置是否是 * 号有很大关系,因为相同半径下,以 * 作为中心点的子串实际包含有用字母更多(实质上 * 只是辅助功能,对于结果没有任何意义,最后要舍弃的),我在这里因为忽略了这个问题而犯了错,比如例子 “abb”,忽略这个问题最后的输出只会是 “b”(中间那个)。
- 第四步:根据记录的最长回文字串中心点位置和半径获取答案
源代码如下:
/* 问题描述
给定一个字符串,找出它的最大回文
假定字符串最大长度为1000
*/
/* From https://leetcode.com/problems/longest-palindromic-substring/description/ */
/* 2017-09-13 by 王世祺 */
#include<iostream>
using namespace std;
string longestPalindrome(string s) {
if (s.size() == 1)
return s;
if (s.size() > 1000)
return NULL;
string temp;
int longestPos;
int longestLength = -1;
/* Make the length of the string "s" odd by
adding the single "*"
*/
for (int i = 0; i < s.size(); i++) {
temp += s[i];
if (i != s.size() - 1)
temp += '*';
}
/* Record the radius which forms the palindromic for each element in "temp" */
int* radius = new int[2 * s.size() - 3];
int rad;
for (int i = 1; i < temp.size() - 1; i++) {
for (rad = 1; i - rad >= 0 && i + rad < temp.size(); rad++) {
if (temp[i - rad] != temp[i + rad]) {
radius[i - 1] = rad - 1;
rad = 0;
break;
}
}
if (rad != 0)
radius[i - 1] = rad - 1;
/* Record the pos and length of the longest substring we found */
if (radius[i - 1] > longestLength || (radius[i - 1] == longestLength && temp[i] == '*')) {
longestLength = radius[i -1];
longestPos = i;
}
}
string answer;
for (int i = longestPos - radius[longestPos - 1]; i <= longestPos + radius[longestPos - 1]; i++)
if (temp[i] != '*')
answer += temp[i];
delete []radius;
return answer;
}
int main() {
string s = "abb";
cout << longestPalindrome(s);
}
算法复杂度集中在查找每个元素的回文半径上,是二次循环,因此是O(n²)。