题目来源:
https://leetcode.com/problems/longest-palindromic-substring/description/
这道题是很经典的一道题目,笔试中经常会考到,需要认真理解并且懂得如何实现对应的算法。
题目是要求找到一个字符串中最长的回文子串,回文串是什么意思呢?
举个例子如下:
Input: “babad”
Output: “bab”
Note: “aba” is also a valid answer.
就是能够关于中心对称的一个字符串,hannah也是属于一个回文串。
好了,现在我们来看实现,首先一种暴力的方式,我们可以选择直接遍历字符串,然后得到最终的结果。这种实现方式的话需要我们遍历三次字符串,需要使用3个for循环来完成:
class Solution {
public:
string longestPalindrome(string s) {
// 第一层循环是遍历整个字符串
for (int size = s.length(); size > 0; size --){
for (int low = 0, high = low+size-1; high < s.length(); low ++,high ++){
if (checkPalindrome(s, low, high)){
return s.substr(low, high - low + 1);
}
}
}
return s.substr(0, 1);
}
// 判断一个字符串从low到high时是否是回文的
int checkPalindrome(string s, int low, int high){
while(low <= high){
if(s[low] == s[high]){
low ++; high --;
} else {
return false;
}
}
return true;
}
};
另外一种算法的话就是比较著名的mancher算法,很巧妙的一个算法,首先这个算法需要对输入的字符串进行一个转换,将所有的字符串转变成一个奇数长度的字符串,那么这个转换是如何做的呢,很简单,就是将原始字符串的中心插入一个特殊字符,例子如下:
a b a b c b a d
# a # b # a # b # c # b # a # d #
这里选择了#号作为特殊符号进行转化,可以看到通过这种方式,我们可以将所有的字符串转换成奇数长度的字符串,是一个很巧妙的做法,上面的例子是偶数的例子,奇数的例子大家可以自己写一下,也能够实现一样的效果。
然后完成了这个之后,我们再来看一个比较有意思的数组,
str: # a # b # a # b # c # b # a # d #
p[i]: 1 2 1 3 1 3 1 2 1 2 1 2 1 2 1 2 1
这个数组什么意思呢?就是这个处理过的新的字符串计算每个字符的最长回文串的长度,仔细观察这个数组,我们会发现,处理过的字符串包含原始实际数据的对应p[i]数组的回文串长度减去1的就是实际原来字符串的回文串长度,这个规律很神奇。我们就可以利用这个来得到我们原始数组的回文串长度。
那么现在的问题是我们该如何得到这个数组p[i],mancher算法中总结了这个数组的规律,我们来看看它的实现是如何利用这个数组的特点的。
首先我们需要定义几个变量:
1、i :这个变量是我们当前要求的这个数组的值
2、id :已知右边界最大回文子串的中心
3、mx :mx = id + p[id],这个表示的是现在能够达到的最大的右边界
4、j :j = id - (i - id)= 2id -i,这个表示的以id为中心,i的对称点
然后我们需要两个图来解释这几个变量的作用:
第一张图片,这里的id就是我们已经计算得到的p数组最大的数,也就是上面所说的最大回文子串的中心,这个是我们要求的是i这个位置的值是多少,我们可以得到 i 的对称点 j,这个时候以 j 为中心的回文子串的长度是位于id和mx对称点之间的,因为在id的mx到mx对称点之前是回文的,所以我们可以直接得到 i 的位置的回文子串就是等于 j 位置的数值,这个应该是可以理解的。也就是说在这张图的情况下,我们得到的是 p[i] = p[j] 的。
下面这张图的话就是另外一种情况,j 位置的回文半径大于了mx对称点 和 j 之间的距离,那么根据id对称的回文子串,我们可以得到这个时候 i 位置的回文串,至少是 i 到mx之间的大小,超出这部分,我们无法判断,所以在这张图中,我们可以得到这样的式子,p[i] = mx - i
不知道大家到这里能不能明白上面两张图的意思,我们要明确的一点是,在实质意义上我们是通过现有知道的p数组的内容,来求出数组其他位置的数值,这样一步一步往前走,直到得到整个数组的值。
到了这里我们就可以开始写代码了,现在应该看这个代码就会比较好理解了吧:
int p[1000], mx = 0, id = 0;
memset(p, 0, sizeof(p));
for (i = 1; s[i] != '\0'; i++) {
p[i] = mx > i ? min(p[2*id-i], mx-i) : 1;
while (s[i + p[i]] == s[i - p[i]]) p[i]++;
if (i + p[i] > mx) {
mx = i + p[i];
id = i;
}
}
OK!那我们了解了这个算法之后,我们解出这道题就相对比较容易了!
class Solution {
public:
string longestPalindrome(string s) {
string temp;
int p[3000] = {0};
int mx = 0;
int i = 0;
int id = 0;
int max_i = 0;
int len = s.length();
// initial the string
temp.push_back('$');
temp.push_back('#');
for(i = 0; i < len; i++)
{
temp.push_back(s[i]);
temp.push_back('#');
}
// mancher algorithm
for (i = 0; i < temp.length(); i ++){
p[i] = (mx > i) ? min(p[2*id - i], mx - i) : 1;
while(temp[i + p[i]] == temp[i - p[i]]) p[i] ++;
if (mx < i + p[i]){
mx = i + p[i];
id = i;
if (p[i] > p[max_i])
max_i = i;
}
}
int right = (max_i - 1)/2 - (p[max_i] - 1)/2;
return s.substr(right, p[max_i] -1);
}
};