leetcode 5. 最长回文子串 medium
题目描述:
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 1:
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
示例 2:
输入: "cbbd"
输出: "bb"
解题思路一(最暴力O(n^3)):
求一个字符串的最长回文子串,我们可以将以每个字符为首的子串都遍历一遍,判断是否为回文,如果是回文,再判断最大长度的回文子串。算法简单,但是算法复杂度太高,O(n^3)
代码一:
string longestPalindrome(string s)
{
if(s.size()<=1)
return s;
int start=0,maxlength=1;//记录最大回文子串的起始位置以及长度
for(int i=0;i<s.size();i++)
for(int j=i+1;j<s.size();j++)//从当前位置的下一个开始算
{
int left=i,right=j;
for( ;left<right;left++,right--)
{
if(s[left]!=s[right])
break;
}
if(left>=right && j-i+1>maxlength)//这里要注意条件为left>=right,因为如果是偶数个字符,相邻的两个经上一步会出现大于的情况
{
maxlength = j-i+1;
start=i;
}
}
return s.substr(start,maxlength);//利用string中的substr函数来返回相应的子串,第一个参数是起始位置,第二个参数是字符个数
}
解题思路二(小暴力O(n^2)):
遍历字符串,遍历到每个字符的时候看看以这个字符为中心能够产生多大的回文字符串。
不过要注意奇偶情况,由于回文串的长度可奇可偶,比如 "bob" 是奇数形式的回文,"noon" 就是偶数形式的回文,所以对于每一个字符,两种形式的回文都要搜索,对于奇数形式的,我们就从遍历到的字符为中心,向两边进行扩散,而对于偶数情况,我们就把当前位置和下一个位置当作偶数行回文的最中间两个字符,然后向两边进行搜索。
这个算法的时间复杂度是 O(n*n)
class Solution {
public:
string longestPalindrome(string s) {
if (s.size() < 2) return s;
int n = s.size(), maxLen = 0, start = 0;
for (int i = 0; i < n ; ++i) {
// 奇回文
int left=i,right=i;
for(;left>=0 && right<n && s[left]==s[right];--left,++right)
;
if(maxLen < right-left-1){
start=left+1;
maxLen = right-left-1;
}
// 偶回文
left=i,right=i+1;
for(;left>=0 && right<n && s[left]==s[right];--left,++right)
;
if(maxLen < right-left-1){
start=left+1;
maxLen = right-left-1;
}
}
return s.substr(start, maxLen);
}
};
解题思路三(动规O(n^2)):
我们维护一个二维数组 dp,其中 dp[i][j] 表示字符串区间 [i, j] 是否为回文串(=1表示是,=0表示不是),当 i = j 时,只有一个字符,肯定是回文串,如果 i = j + 1,说明是相邻字符,此时需要判断 s[i] 是否等于 s[j],如果i和j不相邻,即 i - j >= 2 时,除了判断 s[i] 和 s[j] 相等之外,dp[i + 1][j - 1] 若为真,就是回文串,通过以上分析,可以写出递推式如下:
dp[i, j] = 1 if i == j
= s[i] == s[j] if j = i + 1
= s[i] == s[j] && dp[i + 1][j - 1] if j > i + 1
初始状态
- dp[i][i]=1
- dp[i][i+1]=1 if str[i]==str[i+1]
class Solution {
public:
string longestPalindrome(string s) {
if(s.size()<2)
return s;
int len=s.size();
vector<vector<int>> dp(len,vector<int>(len,0));
int start=0,maxlen=1;
for(int i=0;i<len;++i){
dp[i][i]=1;
if(i<len-1 && s[i]==s[i+1]){
dp[i][i+1]=1;
start=i;
maxlen=2;
}
} //初始状态
for(int lon=3;lon<=len;++lon){ // 子串长度
for(int i=0;i<=len-lon;++i){ //枚举子串起点
if(s[i]==s[i+lon-1] && dp[i+1][i+lon-2] ){
dp[i][i+lon-1]=1;
if(lon>maxlen){
maxlen=lon;
start=i;
}
}
}
}
return s.substr(start,maxlen);
}
};
解题思路四(马拉车算法 O(n)):
说下关键的记忆点:
1. 先变换字符串 插入#
2. 3个辅助变量
int index=-1;
int R= -1; // R:回文右半径+1, index 关于R的回文中心
vector<int> vec(new_str.size(),0); // vec: 存储回文半径3. 然后遍历新字符串, 关键代码:
vec[i]=R>i?min(vec[2*index-i],R-i):1; // 前面两个是在回文右半径里面的瓶颈 1是暴力扩 (包住了是瓶颈,没包住了是暴力扩)
4. 之后每人再给一次机会
5. 起点和长度怎么算:
return s.substr((res_dx-res_len+1)/2,res_len-1); // 注意起点是中心-半径+1 ,再÷2, 而长度是半径-1
class Solution {
public:
string longestPalindrome(string s) {
if(s.size()<2)
return s;
int len=s.size();
string new_str="#";
for(int i=0;i<len;++i){
new_str =new_str+s[i]+"#";
} // 插入字符
int index=-1;
int R=-1; // R:回文右半径+1, index 关于R的回文中心
int res_dx=0, res_len=0; // res_dx: 最长的中心 res_len: 最长的半径
vector<int> vec(new_str.size(),0); // vec: 存储回文半径
for(int i=0;i<new_str.size();++i){
vec[i]=R>i?min(vec[2*index-i],R-i):1; // 前面两个是在回文右半径里面的瓶颈 1是暴力扩 2*index-i 对称点
while(i+vec[i]< new_str.size() && i-vec[i]>=0){
if(new_str[i+vec[i]]==new_str[i-vec[i]])
++vec[i];
else
break;
} //人人平等都再给你一次机会,但先决条件你没越界
if(i+vec[i]>R){
R=i+vec[i];
index=i;
} // 更新R index
if(vec[i]>res_len){
res_len=vec[i];
res_dx=i;
} // 更新 res
}
return s.substr((res_dx-res_len+1)/2,res_len-1); // 注意起点是中心-半径+1 ,再÷2, 而长度是半径-1
}
};
进阶问题:
在字符串的最后添加最少字符,使整个字符串都成为回文串,其实就是查找在必须包含最后一个字符的情况下,最长的回文子串是什么,那么之后不是最长回文子串的部分逆序后就是应该添加的部分。
具体做法是当R==new_str.size()的时候,break,看左神p541