PTA天梯 L2-008 最长对称子串(动态规划)

对给定的字符串,本题要求你输出最长对称子串的长度。例如,给定Is PAT&TAP symmetric?,最长对称子串为s PAT&TAP s,于是你应该输出11

输入格式:
输入在一行中给出长度不超过 1000 1000 1000的非空字符串。

输出格式:
在一行中输出最长对称子串的长度。

输入样例:

Is PAT&TAP symmetric?

输出样例:

11

此题是一道求最长回文子串的裸题,是一种动态规划问题
这里做区分:最长回文子串和最长回文子序列并不相同,回文子串需要连续,而最长回文子序列不需要连续,但是二者都要满足从前往后的顺序。

对于这道题的动态规划,使用 f [ i ] [ j ] f[i][j] f[i][j]代表从字符串下标为i的字符到下标为j的字符是不是回文字符串,是的,这题最好不要直接用状态转移方程来求最大值属性,而是另开一个变量来记录,而状态转移用的 f [ i ] [ j ] f[i][j] f[i][j]数组变为了bool类型。

拆分进行问题分析:

  • 当字符串长度为1:不用判断,自己本身就是一个回文串,且长度为1
  • 当字符串长度为2,3:对于这个长度的字符串,只要首尾的字符相同,那么就是一个回文串,如果不相同就一定不是回文串
  • 当字符串长度大于3:这时候就需要先判断首尾字符是否相同,如果相同,这时需注意,也不一定为一个回文串,因为中间可能不是,所以这时候使用状态转移方程 f [ i ] [ j ] = f [ i + 1 ] [ j − 1 ] f[i][j] = f[i+1][j-1] f[i][j]=f[i+1][j1],意思就是当前串是否为回文串取决于中间字符串是否为回文串。如果不相同,那么 f [ i ] [ j ] f[i][j] f[i][j]就一定是false,以 i i i开头 j j j结尾的字符串一定不是回文串。

枚举长度和起点:

代码实现:

#include<iostream>
#include<string>
#include<cstring>
using namespace std;
const int N = 1010;
bool f[N][N];
int main() {
	string str;
	getline(cin, str);	//本题包含空格,故使用getline输入

	memset(f, 1, sizeof f);	//先全部初始化为true
	int len = str.length();	//字符串长度

	int maxLength = 1;		//用来记录最大长度的变量

	for (int l = 2; l <= len; l++) {	//枚举长度
		for (int start = 0; start < len; start++) {	//枚举起点
			int end = start + l - 1;	//求出终点下标

			if (end >= len)break;		//如果终点越界,跳出


			//状态转移过程
			if (str[start] != str[end])f[start][end] = 0;
			else {
				if (l <= 3) {
					if (str[start] == str[end])f[start][end] = 1;
				}
				else if (l > 3) {
					if (str[start] == str[end])f[start][end] = f[start + 1][end - 1];
				}
			}

			//判断如果当前串是回文串,那就更新最大长度
			if (f[start][end]) {
				maxLength = l;
			}
		}
	}
	cout << maxLength;
	return 0;
}

如果题目要求输出最长回文串,那么就可以在代码中加入变量记录最长回文串的起点和终点。对于数字串,道理也是相同的。

同时也可以枚举起点和终点

#include<iostream>
#include<string>
using namespace std;
const int N = 1010;
int f[N][N];

int main() {
	string str;
	getline(cin, str);

	int len = str.length();
	int maxLength = 1;

	for (int j = 0; j < len; j++) {
		for (int i = 0; i <= j; i++) {
			if (j - i <= 2) {
				if (str[i] == str[j])f[i][j] = 1;
			}
			else {
				if (str[i] == str[j])f[i][j] = f[i + 1][j - 1];
				else f[i][j] = 0;
			}

			if (f[i][j]) {
				maxLength = max(maxLength,j-i+1);
			}
		}
	}
	cout << maxLength;
	return 0;
}

接下来是求最长回文子序列:

对于求最长回文子序列问题,使用 f [ i ] [ j ] f[i][j] f[i][j]来代表以i为起点j为终点的最长的回文子序列的长度,这时候划分为 i i i j j j相等和不相等两种情况,如果相等,那么这个回文子序列的长度就是由他的中间部分的最长的回文子序列的长度直接加2,也就是 f [ i + 1 ] [ j − 1 ] + 2 f[i+1][j-1] + 2 f[i+1][j1]+2,如果不相等的话,因为同时包含两个字符肯定无法构成回文子序列,所以就在去掉开头和结尾的字符串里面找最大值。

代码:

#include<iostream>
#include<string>
#include<cstring>
using namespace std;
const int N = 1010;

int f[N][N];

int main() {
	string str; getline(cin, str);
	
	int len = str.length();

	for (int i = 0; i < len; i++) {
		for (int j = 0; j < len; j++) {
			f[i][j] = 1;
		}
	}

	for (int i = len - 2; i >= 0; i--) {	//需要倒着遍历,不然会被其他部分影响
		for (int j = i + 1; j < len; j++){
			if (str[i] == str[j])f[i][j] = f[i + 1][j - 1] + 2;
			else f[i][j] = max(f[i][j - 1], f[i + 1][j]);
		}
	}
	cout << f[0][len - 1];
	return 0;
}
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值