最长对称子字符串
最长对称子字符串
http://www.nowcoder.com/questionTerminal/93f6c5b032bf473696373ab0d834b0fc
题解
题目难度:中等难度、经典题目
知识点:字符串、动态数组、动态规划、Manacher法。
##名词解释:
1.子串:由原字符串中任意个连续字符组成的子序列,其长度小于等于原字符串长度。
2.回文:字符对称的文法,有“aba”(单核)和“cabbac”(双核)两种情况。
3.最长回文子串:首先寻找回文子串,其次,判断该回文子串是否为长度最长。
方法一(暴力求解)
思想:从最长的子串开始,遍历所有该原字符串的子串;每找出一个字符串,就判断该字符串是否为回文;子串为回文时,则找到了最长的回文子串,因此结束;反之,则继续遍历。
时间复杂度O(n^3):遍历字符串子串:嵌套一个循环、O(n^2), 判断是否为回文:再次嵌套一个循环、O(n^3)。
#include<iostream>
using namespace std;
string longesPlindrome(string s){
if(s.length()<=1) return s;
//i表示子串长度
for(int i=s.length();i>1;i--){
//j表示子串从原字符串那个下标开始
for(int j=0;j<=s.length()-i;j++){
string sub ;
//得到子串
sub=s.substr(j,i);
int count=0;
//判断子串是否为回文字符串
for(int k=0;k<sub.length()/2;k++){
if(sub[k]==sub[sub.length()-k-1]) count++;
}
if(count==sub.length()/2) return sub;
}
}
}
int main(){
string s;
cin>>s;
string sub=longesPlindrome(s);
cout<<sub;
return 0;
} 方法二:中心扩展算法
思想:
步骤一:中心有2种,一种是一个字母(单核),另一种是两个字母之间(双核)。也就是奇数长度子串和偶数长度子串。首先确定中心位置i(遍历所有i的情况),分单核和双核两种情况进行步骤二计算,单核:初始low =i,high=i;双核low=i,high = i+1。
步骤二:确定好中心位置后向两个方向扩展,判断low和high是否不超过原字符串的下限和上限,以及low和high处的字符是否相等,相等则low--、high++继续步骤二。否者返回right-left-1,为以该i为中心时能找到的最长回文字符串。
步骤三:判断该次以i为中心的单核、双核情况下得到的最长长度,以及变量maxLen保存的所求得的最大长度比较,将最大值赋值给maxLen变量,并且得到此时最长回文子串sub。
步骤四:遍历完所有中心i后,sub字符串为得到的最长回文子串。
时间复杂度:遍历字符:一层循环、O(n-1);找以当前字符为中心的最长回文子串:嵌套两个独立循环、O(2n*(n-1)) = O(n^2)。
#include<iostream>
using namespace std;
int max(int a,int b){
return a>b ? a: b;
}
int expandAroundCenter(string s,int left, int right){
while(left>=0 && right<s.size() && s[left]==s[right]){
--left;
++right;
}
return right-left-1;
}
string longestPalindrome(string s) {
if(s.empty())
return "";
int startIndex=0;
int maxLen=1;
for(int i=0;i<s.size();++i){
int len1=expandAroundCenter(s,i,i);
int len2=expandAroundCenter(s,i,i+1);
int len=max(len1,len2);
if(len>maxLen){
startIndex=i-(len-1)/2;
maxLen=len;
}
}
return s.substr(startIndex,maxLen);
}
int main(){
string s;
cin>>s;
string sub=longestPalindrome(s);
cout<<sub;
return 0;
}方法三:动态规划:
i,j分别是子串的头索引和尾索引。
对于字符串str,假设dp[i,j]=1表示str[i...j]是回文子串,那个必定存在dp[i+1,j-1]=1。这样最长回文子串就能分解成一系列子问题,可以利用动态规划求解了。首先构造状态转移方程:
上面的状态转移方程表示,当str[i]=str[j]时,如果str[i+1...j-1]是回文串,则str[i...j]也是回文串;如果str[i+1...j-1]不是回文串,则str[i...j]不是回文串。
初始状态:dp[i][i]=1(单核)dp[i][i+1]=1 if str[i]==str[i+1](双核)
时间复杂度:O(n^2)
#include<iostream>
#include<vector>
using namespace std;
string longestPalindrome(string s)
{
if (s.empty()) return "";
int len = s.size();
if (len == 1)return s;
int longest = 1;
int start=0;
vector<vector<int> > dp(len,vector<int>(len));
for (int i = 0; i < len; i++)
{
dp[i][i] = 1;
if(i<len-1)
{
if (s[i] == s[i + 1])
{
dp[i][i + 1] = 1;
start=i;
longest=2;
}
}
}
for (int l = 3; l <= len; l++)//子串长度
{
for (int i = 0; i+l-1 < len; i++)//枚举子串的起始点
{
int j=l+i-1;//终点
if (s[i] == s[j] && dp[i+1][j-1]==1)
{
dp[i][j] = 1;
start=i;
longest = l;
}
}
}
return s.substr(start,longest);
}
int main(){
string s;
cin>>s;
string sub=longestPalindrome(s);
cout<<sub;
return 0;
}方法(四)Manacher法
这是一个专门用作处理最长回文子串的方法,这里直接借用了别人的讲解方法。其实主要思想是,把给定的字符串的每一个字母当做中心,向两边扩展,这样来找最长的子回文串,这个叫中心扩展法,但是这个方法还要考虑到处理abba这种偶数个字符的回文串。Manacher法将所有的字符串全部变成奇数个字符。
时间复杂度为:O(n)
#include<iostream>
#include<vector>
using namespace std;
string longestPalindrome(string s)
{
string manaStr = "$#";
for (int i=0;i<s.size();i++) //首先构造出新的字符串
{
manaStr += s[i];
manaStr += '#';
}
vector<int> rd(manaStr.size(), 0);//用一个辅助数组来记录最大的回文串长度,注意这里记录的是新串的长度,原串的长度要减去1
int pos = 0, mx = 0;
int start = 0, maxLen = 0;
for (int i = 1; i < manaStr.size(); i++)
{
rd[i] = i < mx ? min(rd[2 * pos - i], mx - i) : 1;
while (i+rd[i]<manaStr.size() && i-rd[i]>0 && manaStr[i + rd[i]] == manaStr[i - rd[i]])//这里要注意数组越界的判断,源代码没有注意,release下没有报错
rd[i]++;
if (i + rd[i] > mx) //如果新计算的最右侧端点大于mx,则更新pos和mx
{
pos = i;
mx = i + rd[i];
}
if (rd[i] - 1 > maxLen)
{
start = (i - rd[i]) / 2;
maxLen = rd[i] - 1;
}
}
return s.substr(start, maxLen);
}
int main(){
string s;
cin>>s;
string sub=longestPalindrome(s);
cout<<sub;
return 0;
}
联想公司福利 1493人发布