腾讯2017年实习生招聘编程题——构造回文

虽然这是实习生招聘的第一题,但是对我这种不太了解动态规划和LCS(Longest Common Sequence)相关算法的学渣来说,还是有比较多坑的......只隐约记得以前在SOJ见过类似的题目,但是当时比较懒,没有仔细钻研,没想到现在还是得补回来。

题目如下:



乍一看好像没什么好的方法,但是仔细思考一下,会发现这题真的可以“换个方向”来看。既然题目要求得到的是回文串,那么我们可以联想到,回文的性质是不管正着读还是反着读都是相同的。那么我们是否可以先把原字符串进行一个反转操作,然后再求它和原文的最长公共序列(LCS)呢?(注意:最长公共序列可以由不连续的字符组成)

比如一个字符串abcda,反转后得到adcba, 最长公共序列为a+c+a = aca这个字符串。我们发现,这个LCS同时也符合回文的性质。

可能有人会问,如果最大公共序列不是回文呢?我个人的想法是,两个顺序完全相反的字符串,出现这种情况的可能性为0,如果有反例也欢迎各位举出。

有了这个思路,但还是不会做,因为之前没有接触过求LCS的算法(好吧暴露了算法导论看几页就弃的事实 _(:з)∠)_),所以只能上某度搜索相关文献,结果就找到了一个叫做Needleman/Wunsch算法的东西。一番钻研过后发现思想还是挺巧妙的,类似于我高中学生物时候老师教的算遗传概率的棋盘法(但是学术意义差远了hhh)。详细的算法就不在这里介绍,有兴趣的朋友可以看看这篇:https://wenku.baidu.com/view/4b534527f524ccbff12184c9.html,但是里面有些东西比较多余,代码里没有实现(比如方向数组)。

扯了这么多,具体实现如下,用DP的思想还是比较简单的:

#include <iostream>
#include <algorithm>

using namespace std;

int Lcs[1001][1001];
int LCS(string s1, string s2)
{
    int M = s1.length();
    int N = s2.length();
    //int Lcs[M][N];
    //初始化LCS矩阵, 当i或j为0时, 说明两个序列中有一个为空, LCS=0
    for(int i = 0; i < M; i++){
        Lcs[i][0] = 0;
    }
    for(int j = 0; j < N; j++){
        Lcs[0][j] = 0;
    }
    //Needleman/Wunsch算法
    for(int i = 1; i <= M; i++){
        for(int j = 1; j <= N; j++){
            if(s1[i - 1] == s2[j - 1])
                //如果匹配到相同的字符, 则在LCS长度加1, 最新的LCS长度储存在左上
                //因此取左上角的值加1
                Lcs[i][j] = Lcs[i - 1][j - 1] + 1;
            else
                //如果这个字符时某个字符串独有的, 则LCS长度保持不变, 取上方和左方元素的较大值(舍去较短的CS)
                Lcs[i][j] = max(Lcs[i - 1][j], Lcs[i][j - 1]);
        }
    }
    //返回右下角元素(LCS长度)
    return Lcs[M][N];
}

int main()
{
    string test;
    while(cin >> test){
        string rtest;
        int L = test.length();
        for(int i = L - 1; i >= 0; i--){
            rtest += test[i];
        }
        int Strip_len = LCS(test, rtest);
        cout << L - Strip_len << endl;
    }
}
题目说了字符串长度不超过1k,我一高兴直接在函数体里面定义了一个MxN的数组,在OJ上运行的结果当然就是爆内存 (’ _ ')...后来只能用大一时候学的笨方法,在外面定义一个全局数组,大小开的是1000x1000。

还有一个坑就是LCS矩阵初始化的时候,最左侧一列和最上方一行是全为0的(姑且称之为外围),代表s1和s2至少有一个为空序列的情况。所以在跑算法的时候直接从Lcs[1][1]开始,但是计算元素的值依然需要用到外围元素,所以循环下标从1开始,但是if条件中的下标是从0开始的(讲得有点混乱,不过大家看懂算法的话应该都能get到)。

最后记得返回右下角的元素Lcs[M][N],而不是Lcs[M-1][N-1](也就只有你会这样吧 -  _ -)。

最后用原字符串长度减去最大公共序列长度即可,不要直接把求得的最大回文串长度给输出了。

这个算法虽然实现起来代码不长,但是可以预见在应对大文本时它所需求的内存空间有多么巨大(空间复杂度达到了O(MN))。果然内存和代码效率不可兼得 Orz。

据说还有一种暴力枚举的方法,但回想起初学代码时被暴力破解法支配的恐惧,还是不碰为妙hhh。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值