题目描述
Catcher是MCA国的情报员,他工作时发现敌国会用一些对称的密码进行通信,比如像这些ABBA,ABA,A,123321,但是他们有时会在开始或结束时加入一些无关的字符以防止别国破解。比如进行下列变化 ABBA->12ABBA,ABA->ABAKK,123321->51233214 。因为截获的串太长了,而且存在多种可能的情况(abaaab可看作是aba,或baaab的加密形式),Cathcer的工作量实在是太大了,他只能向电脑高手求助,你能帮Catcher找出最长的有效密码串吗?
输入描述:
输入一个字符串
输出描述:
返回有效密码串的最大长度
输入例子:
ABBA
输出例子:
4
此题实际上是一个求字符串中最长回文长度问题,最常见的是manacher算法。为便于处理,将原字符串中其它非英文、数字字符全部去掉(可以不要这步):
for(auto &c:str){
if((c>='A'&&c<='Z')||(c>='a'&&c<='z')||(c>='0'&&c<='9'))
continue;
else//非英文和数字字符标记
c='~';
}
auto iter=remove(str.begin(),str.end(),'~');
str.erase(iter,str.end());//删除
接下来就是实现manacher()并调用,完整AC过的代码:
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
//manacher算法求回文,返回最长回文字符串
string manacher(string s) {
// Insert '#'
string t = "$#";
for (int i = 0; i < s.size(); ++i) {
t += s[i];
t += "#";
}
// Process t
vector<int> p(t.size(), 0);
int mx = 0, id = 0, resLen = 0, resCenter = 0;
for (int i = 1; i < t.size(); ++i) {
p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;
while (t[i + p[i]] == t[i - p[i]]) ++p[i];
if (mx < i + p[i]) {
mx = i + p[i];
id = i;
}
if (resLen < p[i]) {
resLen = p[i];
resCenter = i;
}
}
return s.substr((resCenter - resLen) / 2, resLen - 1);
}
int main()
{
string str;
while(getline(cin,str)){
for(auto &c:str){
if((c>='A'&&c<='Z')||(c>='a'&&c<='z')||(c>='0'&&c<='9'))
continue;
else//非英文和数字字符
c='~';
}
auto iter=remove(str.begin(),str.end(),'~');
str.erase(iter,str.end());
//cout<<str<<endl;
cout<<manacher(str).size()<<endl;
}
return 0;
}
补:回文串的判断方法,判断一个字符串是否是回文串:
bool judgeHuiwen(string str){
int size=str.size();
for(int j=0;j<(size+1)/2;j++){
if(str[j]!=str[size-1-j]){
return false;
}
}
return true;
}
判断方法很简单,从两头开始比较字符,出现不等则非回文串,但是注意循环结束条件,两头向中间靠拢时只需遍历(size+1)/2次,size为字符串的长度。
根据该方法写了个暴力枚举算法:长度由长到短,对字符串取对应长度的所有可能子串,依次判断是否为回文串,若是则直接返回,该串一定是第一个最长的回文子串。
可惜该方法因算法复杂度过高,牛客OJ上未能AC。
#include <iostream>
using namespace std;
bool judgeHuiwen(string str){
int size=str.size();
for(int j=0;j<(size+1)/2;j++){
if(str[j]!=str[size-1-j]){
return false;
}
}
return true;
}
string findMaxHuiWenSubStr(string str){
string res;
for(int len=str.size();len>2;len--){
for(int i=0;i<=str.size()-len;i++){
string subStr=str.substr(i,len);
auto size=subStr.size();
if(judgeHuiwen(subStr)){//是回文串
res=subStr;
goto Res;
}
}
}
Res:
return res;
}
int main()
{
string str;
while(cin>>str){
string res;
res=findMaxHuiWenSubStr(str);
cout<<res.size()<<endl;
}
return 0;
}
-----------------------------2017/2/24 更新----------------------------
上次自己写的一个判断方法复杂度过高,今天发现一种新的解法成功通过OJ,比较容易理解:
保存原字符串的副本,将其反转后按长度递减取子串(跟前面介绍的思路类似,不过是先反转后取子串),再判断原串中是否包含该子串,一旦成功则直接返回结果一定是最长回文子串。
string getMaxHuiwenStrLen(string str){
string res;
string temp=str;
reverse(temp.begin(),temp.end());
//获取所有子串,查看翻转后的字符串是否包含该子串
for(int len = temp.length(); len >= 1 ;len--){
//len为子串的长度
for(int j = 0; j <= temp.length()-len;j++){
string subStr = temp.substr(j,len);
if(str.find(subStr)!=string::npos){
return subStr;
}
}
}
return res;
}
另外,此题也是一道经典动态规划问题(与最长公共子串有相似之处),后补。