对给定的字符串,本题要求你输出最长对称子串的长度。例如,给定Is PAT&TAP symmetric?
,最长对称子串为s PAT&TAP s
,于是你应该输出11。
输入格式:
输入在一行中给出长度不超过1000的非空字符串。
输出格式:
在一行中输出最长对称子串的长度。
输入样例:
Is PAT&TAP symmetric?
输出样例:
11
一、暴力枚举 (直接三层循环就可以)o(n^3)
#include <iostream>
#include <string>
using namespace std;
// 检查子串 s[left...right] 是否为回文
bool isPalindrome(const string& s, int left, int right) {
while (left < right) {
if (s[left] != s[right]) {
return false;
}
left++;
right--;
}
return true;
}
int longestSymmetricSubstring(const string& s) {
int n = s.length();
if (n == 0) return 0;
int maxLen = 1; // 至少一个字符是对称的
// 枚举所有子串
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
// 检查子串 s[i...j] 是否为回文
if (isPalindrome(s, i, j)) {
int currentLen = j - i + 1;
if (currentLen > maxLen) {
maxLen = currentLen;
}
}
}
}
return maxLen;
}
int main() {
string s;
getline(cin, s);
cout << longestSymmetricSubstring(s) << endl;
return 0;
}
二、动态规划
-
定义状态:
dp[i][j]
表示字符串从第i
个字符到第j
个字符是否为对称子串。 -
状态转移方程:
-
如果
s[i] == s[j]
并且dp[i+1][j-1]
为true
,那么dp[i][j]
也为true
。 -
如果
i == j
,那么dp[i][j]
为true
(单个字符是对称的)。 -
如果
j == i + 1
并且s[i] == s[j]
,那么dp[i][j]
为true
(两个相同字符是对称的)。
-
-
初始化:所有单个字符的子串都是对称的,即
dp[i][i] = true
。 -
结果:在填充
dp
表的过程中,记录最长的对称子串的长度。
#include<bits/stdc++.h>
using namespace std;
void solve(){
string s;
getline(cin,s);
int n=s.length();
if(n==0)cout<<0<<endl;
else{
vector<vector<bool>>dp(n,vector<bool>(n,false));
int maxlen=1;
for(int i=0;i<n;i++){
dp[i][i]=true;
}
// 检查长度为2的子串
for(int i=0;i<n-1;i++){
if(s[i]==s[i+1]){
dp[i][i+1]=true;
maxlen=2;
}
}
for(int length=3;length<=n;length++){
for(int i=0;i<n-length+1;i++){
int j=i+length-1;
if(s[i]==s[j]&&dp[i+1][j-1])
{
dp[i][j]=true;
maxlen=max(maxlen,length);
}
}
}
cout<<maxlen<<endl;
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
solve();
return 0;
}
三、中心扩展法
-
对称子串的中心:
-
对称子串可以是奇数长度(中心是一个字符),例如
aba
。 -
对称子串也可以是偶数长度(中心是两个字符),例如
abba
。
-
-
遍历所有可能的中心:
-
对于每个字符,作为奇数长度对称子串的中心,向左右扩展。
-
对于每两个相邻字符,作为偶数长度对称子串的中心,向左右扩展。
-
-
记录最大长度:在扩展过程中,记录最长的对称子串的长度。
#include<bits/stdc++.h>
using namespace std;
int expand(string s,int left,int right){
while(left>=0&&right<s.length()&&s[left]==s[right]){
left--;
right++;
}
return right-left-1;
}
void solve(){
string s;
getline(cin,s);
int n=s.length();
if(n==0)cout<<0<<endl;
else{
int maxlen=1;
for(int i=0;i<n;i++){
int len1=expand(s,i,i);//奇数长度的对称子串
int len2=expand(s,i,i+1);//偶数长度的对称子串
int currentmax=max(len1,len2);
maxlen=max(maxlen,currentmax);
}
cout<<maxlen<<endl;
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
solve();
return 0;
}
四、马拉车算法(利用回文串的对称性,避免了重复计算)
-
预处理:
-
将原始字符串插入特殊字符(如
#
),使得所有回文子串都变为奇数长度。例如,aba
变成#a#b#a#
,abba
变成#a#b#b#a#
。 -
这样,无论是奇数长度还是偶数长度的回文子串,都可以统一处理。
-
-
利用对称性:
-
维护一个数组
p
,其中p[i]
表示以字符s[i]
为中心的最长回文半径(包括中心字符)。 -
维护两个变量:
-
C
:当前已知的最长回文子串的中心。 -
R
:当前已知的最长回文子串的右边界。
-
-
对于每个字符
s[i]
,利用对称性快速计算p[i]
。
-
-
快速计算
p[i]
:-
如果
i
在R
的左侧,则p[i]
的初始值可以通过对称性确定为min(R - i, p[2 * C - i])
。 -
如果
i
在R
的右侧,则从p[i] = 1
开始扩展。 -
扩展
p[i]
直到字符不匹配或超出边界。
-
-
更新
C
和R
:-
如果以
i
为中心的回文子串的右边界超过了R
,则更新C = i
和R = i + p[i]
。
-
-
结果:
-
最终,
p
数组中的最大值即为最长回文子串的长度。
-
#include<bits/stdc++.h>
using namespace std;
void solve(){
string s;
getline(cin,s);
int maxlen=0;
string t="#";
for(char ss:s){
t+=ss;
t+='#';
}
int n=t.length();
vector<int>p(n);
int c=0;
int r=0;
for(int i=0;i<n;i++){
if(i<r){
p[i]=min(r-i,2*c-i);
}
while(i+p[i]+1<n&&i-p[i]-1>=0&&t[i+p[i]+1]==t[i-p[i]-1]){
p[i]++;
}
if(i+p[i]>r){
r=i+p[i];
c=i;
}
maxlen=max(maxlen,p[i]);
}
cout<<maxlen<<endl;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
solve();
return 0;
}
if (i < R) { p[i] = min(R - i, p[2 * C - i]); }
-
i < R
:如果当前字符t[i]
在当前最长回文子串的右边界R
的左侧,则可以利用对称性快速初始化p[i]
。 -
2 * C - i
:这是字符t[i]
关于中心C
的对称点j
。 -
p[j]
:以字符t[j]
为中心的最长回文半径。 -
R - i
:当前字符t[i]
到右边界R
的距离。 -
min(R - i, p[j])
:取R - i
和p[j]
的较小值,作为p[i]
的初始值。
为什么取较小值?
-
如果
p[j]
较小,说明以t[j]
为中心的回文子串完全包含在当前最长回文子串内,因此p[i]
可以直接等于p[j]
。 -
如果
p[j]
较大,说明以t[j]
为中心的回文子串可能超出了当前最长回文子串的范围,因此p[i]
只能初始化为R - i
,因为超出R
的部分还未检查。
示例说明
假设预处理后的字符串为 t = "#a#b#a#"
,当前最长回文子串的中心 C = 3
,右边界 R = 5
。
-
对于
i = 4
:-
对称点
j = 2 * C - i = 2 * 3 - 4 = 2
。 -
p[j] = p[2] = 1
(以t[2] = 'b'
为中心的最长回文半径)。 -
R - i = 5 - 4 = 1
。 -
因此,
p[i] = min(1, 1) = 1
。
-
-
对于
i = 5
:-
对称点
j = 2 * C - i = 2 * 3 - 5 = 1
。 -
p[j] = p[1] = 1
(以t[1] = 'a'
为中心的最长回文半径)。 -
R - i = 5 - 5 = 0
。 -
因此,
p[i] = min(0, 1) = 0
。
-
方法 | 时间复杂度 | 空间复杂度 | 适用场景 |
---|---|---|---|
暴力枚举法 | O(n³) | O(1) | 简单实现,适合小规模字符串 |
动态规划 | O(n²) | O(n²) | 需要记录所有子串是否为回文 |
中心扩展法 | O(n²) | O(1) | 简单实现,空间复杂度低 |
马拉车算法 | O(n) | O(n) | 最优解,适合大规模字符串 |