回文拼接是字符串处理中的经典问题,题目要求判断一个字符串能否被拆分成两个非空回文子串。这个问题在算法竞赛和编程能力认证考试中经常出现,考察选手对字符串处理和回文特性的理解。
回文是指正读反读都相同的字符串,比如"aba"、"abba"、"a"都是回文。回文拼接问题的核心在于找到一个分割点,使得分割后的两个子串都满足回文性质。
给定一个字符串s,判断是否存在一个位置j(2 ≤ j ≤ len-2),使得:
例如:
最直观的解法是枚举所有可能的分割点,然后检查分割后的两个子串是否都是回文。这正是题目给出的C++代码采用的思路。
算法步骤:
cpp复制#include<bits/stdc++.h>
using namespace std;
int main() {
int n;
cin>>n; // 输入测试用例数量
string s;
for(int i=1;i<=n;i++){
cin>>s; // 输入待检查的字符串
int len=s.length();
bool f=false; // 标记是否找到合适的分割
// 枚举所有可能的分割点
for(int j=2;j<=len-2;j++){
string str1=s.substr(0,j); // 前j个字符
string str2=s.substr(j,len); // 剩余字符
// 检查str1和str2是否都是回文
string st1=str1;
string st2=str2;
reverse(st1.begin(),st1.end());
reverse(st2.begin(),st2.end());
if(str1==st1&&str2==st2){
f=true;
break; // 找到就提前退出
}
}
cout<<(f?"Yes":"No")<<endl;
}
return 0;
}
该算法的时间复杂度为O(n×m²),其中:
对于GESP三级考试来说,这个复杂度是可以接受的,因为题目通常会限制字符串长度在合理范围内。
暴力解法虽然简单,但效率不高。我们可以通过预处理来优化:
预处理每个子串是否是回文,使用动态规划:
预处理完成后,只需要检查是否存在j使得dp[0][j-1]和dp[j][len-1]都为true
这种优化将时间复杂度降为O(n×m²)预处理 + O(n×m)查询,虽然最坏情况下复杂度相同,但实际运行效率会更高。
cpp复制#include<bits/stdc++.h>
using namespace std;
int main() {
int n;
cin>>n;
string s;
for(int i=1;i<=n;i++){
cin>>s;
int len=s.length();
bool f=false;
// 预处理回文信息
vector<vector<bool>> dp(len, vector<bool>(len, false));
for(int l=1;l<=len;l++){ // 子串长度
for(int start=0;start+l-1<len;start++){ // 子串起始位置
int end=start+l-1;
if(l==1) dp[start][end]=true;
else if(l==2) dp[start][end]=(s[start]==s[end]);
else dp[start][end]=(s[start]==s[end])&&dp[start+1][end-1];
}
}
// 检查分割点
for(int j=2;j<=len-2;j++){
if(dp[0][j-1]&&dp[j][len-1]){
f=true;
break;
}
}
cout<<(f?"Yes":"No")<<endl;
}
return 0;
}
在实现回文拼接算法时,特别需要注意边界条件:
注意:在实际编程竞赛中,一定要仔细阅读题目描述,明确输入输出的具体要求,包括字符串长度的范围、特殊情况的处理等。
对于非常长的字符串,可以考虑以下优化:
内存优化:预处理dp数组时,可以使用一维数组或者位压缩来减少空间使用
回文问题在算法竞赛和面试中都非常常见,掌握回文的各种性质和算法对提升编程能力很有帮助。建议从简单的问题入手,逐步挑战更复杂的回文相关题目。