虽然这是实习生招聘的第一题,但是对我这种不太了解动态规划和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。