吉林大学大四学生 计蒜客OJ MCoffee 2020.11.12
题目
给定一个字符串,输出所有长度至少为 2 的回文子串。
回文子串即从左往右输出和从右往左输出结果是一样的字符串,比如:abba,cccdeedccc都是回文字符串。
输入格式
一个字符串,由字母或数字组成。长度 500 以内。
输出格式
输出所有的回文子串,每个子串一行。
子串长度小的优先输出,若长度相等,则出现位置靠左的优先输出。
样例数据
样例输入:123321125775165561
样例输出:
33
11
77
55
2332
2112
5775
6556
123321
165561
分析过程
这道题是我留给孩子们的一个作业。在此声明,这是我的失误。
原因?
一开始的时候,我看到这道题,认为这应该是一道普及T1难度的题目。因为无论从哪里看来,关键词都显得很Easy
- 字符串
- 回文
- 单行 无空格 有限 可知 输入
- 长度500
看到这些的时候,我下意识忽略了其中包括的一些细节,直接布置了这个作业。
万幸的是,我在课间的时候再次浏览了一下这道题。意识到这道题里面藏了一点好玩的(好van♂的 )东西。
这些东西,分别是
- 字符串排序
- 递归的解题思路
- 时间代价的上限和下限
emmmm,难了点,留当思考作业叭。
为了制作标准答案,我下课之后,就开始写这道题……
……
越写越不对劲……
从头说起。这道题在我当时看来有这样几种方式解决
在我当时看来
在我当时看来
第一种方式:硬写
属于废话。
但是为什么不行,得分析一下。
字符串长度500,其中只包括字母数字,没有空格回车。
对于长度为500的字符串来说,里面最多有多少字串呢?
应该是N+N-1+……+1 一共是n*(n+1)/2 小学二年级的题
代入得12500左右是吧,感觉还行。
但是!!但是!!! 字符串最朴素的比较方式的话,要一一对应去比较,也就是长度为N的要比较N次
公式变为N*1+(N-1)* 2+……+ 1*N
这个公式结果为N(2N + 1)(N + 1) / 6. 小学三年级学的
代入的话,50000000左右 五千万
这种数,,挺尴尬的,giligili(刚刚)够用
我是不太喜欢这种,防爆性能也一般
第二种方式:递归+排序
那么,从一个回文子串逐渐扩展怎么样呢?
我先找一个长度为2的,然后看两边字符是否相同,相同的话也是一个子串,这样递归下去。
看起来非常合理。时间上非常节省,不用判断那些没用的地方,而且500个点,最多499个长度为2的子串。然后递归执行次数的话,长度每次扩展2,250次。所以这种方式只需要用500*250次,非常优秀。
但是!!但是!!!
找到了,怎么排序??毕竟题目要求,先排长度,再拍顺序,得用比较吧
好家伙,最多12500个子串,得用快排
所以得用结构体快速排序
还行吧,可以接受。
第三种方式:DP
没错,你没看错,这么简单的题,我用上动态规划了
啪叽啪叽(鼓掌)
具体的心路历程,大概是……
我找到长度为2的子串了,那我直接都找完不就完事了
找完后面长度为4的怎么找? 之前2的子串我记录下位置,然后对应扩展不就完事了
那我记录下来假如 x,y,z的位置,我第二排怎么用呢?假如直接扩展又变成递归+排序了
所以,我用DP【I】【J】来表示,长度为i的,起始位置为j的串,是否为回文串
s[j-1]==s[j+i],并且 dp[i][j]为TRUE 则 DP[i+2][j-1]=TRUE
吧唧吧唧,DP递推式出现了
那么,长度为奇数的ok么?当然。因为只和起始位置和长度有关,奇偶性不影响DP递推
至此,SOLVE
我们简单的归纳一下回文串的性质
- 回文串前半段和后半段镜像对称
- 回文串左右去掉相同长度,剩余的依旧是回文串
- s[0]=s[len-1] s[i]=s[len-i-1]
- 没了
这道题用DP好像是大材小用了,但是因为解题思路非常合适所以代码量,代码难度,都非常优秀。下面是AC代码展示
//吉林大学大四学生 计蒜客OJ MCoffee 2020.11.12 Wechat MythLucky
代码展示
//字符串 回文子串
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
bool dp[600][600];
int main()
{
char s[600];
int len,i,j,k;
cin>>s;
len=strlen(s);
for(i=0;i<=len;i++)
{
dp[0][i]=true;
dp[1][i]=true;
}
for(i=2;i<=len;i++)
{
for(j=0;j<=len-i;j++)
{
if((s[j]==s[j+i-1]) && (dp[i-2][j+1]))
{
dp[i][j]=true;
for(k=0;k<=i-1;k++)
{
cout<<s[j+k];
}
cout<<endl;
}
}
}
return 0;
}