7-1 最长对称子串 (25 分)
对给定的字符串,本题要求你输出最长对称子串的长度。例如,给定Is PAT&TAP symmetric?
,最长对称子串为s PAT&TAP s
,于是你应该输出11。
输入格式:
输入在一行中给出长度不超过1000的非空字符串。
输出格式:
在一行中输出最长对称子串的长度。
输入样例:
Is PAT&TAP symmetric?
输出样例:
11
方法一:直接暴力枚举
求每一个子串时间复杂度O(N^2), 判断子串是不是回文O(N),两者是相乘关系,所以时间复杂度为O(N^3)。
算法代码:
#include <iostream>
using namespace std;
string longestPalindrome(string &s)
{
int len = s.size(); //字符串长度
int maxlen = 1; //最长回文字符串长度
int start = 0; //最长回文字符串起始地址
for(int i = 0; i < len; i++) //起始地址
{
for(int j = i + 1; j < len; j++) //结束地址
{
int tmp1 = i, tmp2 = j;
while(tmp1 < tmp2 && s.at(tmp1) == s.at(tmp2))//判断是不是回文
{
tmp1++;
tmp2--;
}
if(tmp1 >= tmp2 && j - i + 1 > maxlen)
{
maxlen = j - i + 1;
start = i;
}
}
}
return s.substr(start, maxlen);
}
int main()
{
string s;
cout << "Input source string: ";
cin >> s;
cout << "The longest palindrome: " << longestPalindrome(s);
return 0;
}
此外补充一种非常好的方法:(强烈推荐)
分析:有两种可能,一种是回文字符串的长度为奇数,一种是偶数的情况。i为字符串当前字符的下标。
当回文字串为奇数的时候,j表示i-j与i+j构成的回文字串长度;当回文字串长度为偶数的时候,j表示i+1左边j个字符一直到i右边j个字符的回文字串长度~~~
用maxvalue保存遍历结果得到的最大值并且输出~~
这种算法思路非常清晰且易懂
#include <bits/stdc++.h>
using namespace std;
int main()
{
string ss;
getline(cin,ss);
int temp,maxvalue=0;
int len = ss.size()
for(int i = 0; i < len; i++)
{
temp = 1;
///回文串为奇数
for(int j = 1; j < len; j++)
{
if(i - j < 0 || i + j >= len || s[i - j] != s[i + j])
break;
temp += 2;
}
///回文串为偶数
maxvalue = temp > maxvalue ? temp : maxvalue;
temp = 0;
for(int j = 1; j < len; j++)
{
if(i - j + 1 < 0 || i + j >= len || s[i - j + 1] != s[i + j])
break;
temp += 2;
}
maxvalue = temp > maxvalue ? temp : maxvalue;
}
cout << maxvalue;
}
方法二:动态规划
分析:有两种可能,一种是回文字符串的长度为奇数,一种是偶数的情况。i为字符串当前字符的下标。
当回文字串为奇数的时候,j表示i-j与i+j构成的回文字串长度;当回文字串长度为偶数的时候,j表示i+1左边j个字符一直到i右边j个字符的回文字串长度~~~
用maxvalue保存遍历结果得到的最大值并且输出~~
这种算法思路非常清晰且易懂,但时间复杂度较大。求每一个子串时间复杂度O(N^2), 判断子串是不是回文O(N),两者是相乘关系,所以时间复杂度为O(N^3)。
#include <iostream>
using namespace std;
int main() {
string s;
getline(cin, s);
int maxvalue = 0, temp;
int len = s.length();
for(int i = 0; i < len; i++) {
temp = 1;
///回文串为奇数
for(int j = 1; j < len; j++) {
if(i - j < 0 || i + j >= len || s[i - j] != s[i + j])
break;
temp += 2;
}
///回文串为偶数
maxvalue = temp > maxvalue ? temp : maxvalue;
temp = 0;
for(int j = 1; j < len; j++) {
if(i - j + 1 < 0 || i + j >= len || s[i - j + 1] != s[i + j])
break;
temp += 2;
}
maxvalue = temp > maxvalue ? temp : maxvalue;
}
cout << maxvalue;
return 0;
}
方法二:动态规划
下面介绍动态规划的方法,使用动态规划可以达到最优的 O(n2) 复杂度。
令 dp[i][j] 表示 S[i] 至 S[j] 所表示的子串是否是回文子串,是则为 1,不是则为 0。这样根据 S[i] 是否等于 S[j] ,可以把转移情况分为两类:
-
- 若 S[i] == S[j],那么只要 S[i+1] 至 S[j-1] 是回文子串,S[i] 至 S[j] 就是回文子串;如果S[i+1] 至 S[j-1] 不是回文子串,则 S[i] 至 S[j] 也不是回文子串。
- 若 S[i] != S[j],那么 S[i] 至 S[j] 一定不是回文子串。
由此可以写出状态转移方程:
dp[i][j]={dp[i+1][j−1],S[i]==S[j]0,S[i]!=S[j]dp[i][j]={dp[i+1][j−1],S[i]==S[j]0,S[i]!=S[j]
边界:dp[i][i]=1,dp[i][i+1] = (S[i] == S[i+1]) ? 1 : 0。
根据递推写法从边界出发的原理,注意到边界表示的是长度为 1 和 2 的子串,且每次转移时都对子串的长度减了 1,因此不妨考虑按子串的长度和子串的初始位置进行枚举,即第一遍将长度为 3 的子串的 dp 值全部求出,第二遍通过第一遍结果计算出长度为 4 的子串的 dp 值 ……
算法时间复杂度为O(N ^ 2)。
/*
最长回文子串
*/
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>
#include <stdbool.h>
#define maxn 1010
char S[maxn];
int dp[maxn][maxn];
int main() {
gets(S); // 输入整行字符
int len=strlen(S), ans=1; // ans 记录最长回文子串长度
int i, j, L;
// 边界
for(i=0; i<len; ++i) {
dp[i][i] = 1;
if(i < len-1) {
if(S[i] == S[i+1]) {
dp[i][i+1] = 1;
ans = 2;
}
}
}
// 状态转移方程
for(L=3; L<=len; ++L) { // 枚举子串长度
for(i=0; i+L-1 < len; ++i) { // 枚举子串的起始节点
j = i+L-1; // 子串的右端结点
if(S[i]==S[j] && dp[i+1][j-1]==1) {
dp[i][j] = 1;
ans = L; // 更新最长回文子串长度
}
}
}
printf("%d\n", ans); // 输出
return 0;
}
方法三:中心扩展法
中心扩展就是把给定的字符串的每一个字母当做中心,向两边扩展,这样来找最长的子回文串。算法复杂度为O(N^2)。
需要考虑两种情况:
长度为奇数的回文串,比如a, aba, abcba
长度为偶数的回文串,比如aa, abba
本法借鉴一位大佬的博客:https://www.jianshu.com/p/c82cada7e5b0
给出算法代码,关于此题实现还没来得及写。
#include <iostream>
#include <cstring>
using namespace std;
string longestPalindrome(string &s)
{
const int len = s.size();
int maxlen = 1;
int start = 0;
for(int i = 0; i < len; i++)//求长度为奇数的回文串
{
int j = i - 1, k = i + 1;
while(j >= 0 && k < len && s.at(j) == s.at(k))
{
if(k - j + 1 > maxlen)
{
maxlen = k - j + 1;
start = j;
}
j--;
k++;
}
}
for(int i = 0; i < len; i++)//求长度为偶数的回文串
{
int j = i, k = i + 1;
while(j >= 0 && k < len && s.at(j) == s.at(k))
{
if(k - j + 1 > maxlen)
{
maxlen = k - j + 1;
start = j;
}
j--;
k++;
}
}
return s.substr(start, maxlen);
}
int main()
{
string s;
cout << "Input source string: ";
cin >> s;
cout << "The longest palindrome: " << longestPalindrome(s);
return 0;
}
作者:海天一树X
链接:https://www.jianshu.com/p/c82cada7e5b0
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
方法四:Manacher算法 (马拉车)
Manacher算法的时间复杂度为O(N)
参考博客:https://www.cnblogs.com/z360/p/6375514.html
http://www.cnblogs.com/five20/p/8610733.html
#include <bits/stdc++.h>
using namespace std;
const int maxn =1e6;
string str;
string s_new;
int len[maxn<<1];
int init(string st)
{
int len = st.size();
s_new='$';
//int j = 2;
// cout<<st;
for(int i =1; i <= 2*len; i+=2)
{
s_new += '#';
s_new += st[i/2];
}
s_new+='#';
s_new+='\0';
return 2*len+1;// 返回 s_new 的长度
}
int Manacher(string st,int len_)
{
int mx = 0,ans = 0,po =0;//mx即为当前计算回文串最右边字符的最大值
for(int i =1; i <= len_ ; i++)
{
if(mx>i)
len[i]=min(mx-i,len[2*po-i]);
else
len[i]=1;//如果i>=mx,要从头开始匹配
while(st[i-len[i]]==st[i+len[i]])
len[i]++;
if(len[i]+i>mx)//若新计算的回文串右端点位置大于mx,要更新po和mx的值
{
mx = len[i]+i;
po = i;
}
ans = max(ans,len[i]);//返回Len[i]中的最大值-1即为原串的最长回文子串额长度
}
return ans - 1;
}
int main()
{
getline(cin,str);
int l = init(str);
cout<<Manacher(s_new,l)<<endl;
return 0;
}