字符串 回文子串

吉林大学大四学生 计蒜客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

我们简单的归纳一下回文串的性质

  1. 回文串前半段和后半段镜像对称
  2. 回文串左右去掉相同长度,剩余的依旧是回文串
  3. s[0]=s[len-1] s[i]=s[len-i-1]
  4. 没了

这道题用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;
}
  • 7
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值