华为机试在线训练-牛客网(20)【中级】字符串运用-密码截取

题目描述

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;
}	

另外,此题也是一道经典动态规划问题(与最长公共子串有相似之处),后补。









  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ctrlturtle

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值