动态规划-最长回文子串
最长回文子串
最长回文子串的问题描述:
给出一个字符串 S,求 S 的最长回文子串的长度
例子:
字符串 "PATZJUJZTACCBCC" 的最长回文子串为 "ATZJUJZTA",长度为 9
还是先看暴力解法,枚举子串的两个端点 i 和 j,需要判断在 [i, j]
内的子串是否回文,从复杂度上看,枚举端点需要 O(n2),判断回文需要 O(n),总复杂度为 O(n3)。
那我们介绍一下动态规划下时间复杂度为O(n2) 的一种解法。
令 dp[i][j]
为 S[i] 到 S[j] 是否为回文子串,是则为 1,否则为 0。这样根据 S[i] 是否等于 S[j] ,可以把情况分为两种:
- 如果
S[i] == s[j]
,那么只要S[i+1]
至S[j-1]
是回文子串,S[i]
至S[j]
就是回文子串;如果S[i+1]至S[j-1]
不是回文子串,则S[i]至S[j]
也不是回文子串 - 若
S[i] != S[j]
,那么S[i]至S[j]
一定不是回文子串
由此写出状态转移方程:
if(S[i] == S[j]) dp[i][j] = dp[i+1][j-1]
if(S[i] != S[j]) dp[i][j] = 0
边界条件: dp[i][i] = 1, dp[i][i+1] = (S[i] == S[i+1]) ? 1 : 0
注意这个时候可能出现某些状态始终转移不到的情况,比如说我们 先固定 i, 然后 j 从 2 开始枚举
dp[0][2] => dp[1][1]
初始化过,能够求☑️dp[0][3] => dp[1][2]
初始化过,能够求☑️dp[0][4] => dp[1][3]
未初始化过,不能够求❌
而且不论怎么转移都转移不到这个状态,所以我们必须想出新的枚举方式:
- 根据递推写法从边界出发的原理,注意到边界表示的是长度为 1 和 2 的子串,且每次转移时都对子串长度减 1,因此不妨按照子串的长度和初始位置进行枚举!
- 也就是第一遍把所有长度为 3 的子串枚举出来,第二次枚举长度为 4 的子串 ……
先枚举长度 L (L可以取到字符串长度的),再枚举左端点 i,这样右端点 i + L -1 也能直接得到。
代码实现:
#include <cstdio>
#include <cstring>
const int maxn = 1010;
char S[maxn];
int dp[maxn][maxn];
int main()
{
gets(S);
int len = strlen(S), ans = 1;
memset(dp, 0, sizeof(dp));
// 边界
for(int i = 0; i < len; i++) {
dp[i][i] = 1;
if(i < len - 1) {
if(S[i] == S[i+1]) {
dp[i][i+1] = 1;
ans = 2; // 初始化时注意当前最长回文子串长度
}
}
}
// 状态转移方程
for(int L = 3; L <= len; L++) { // 枚举长度
for(int i = 0; i + L - 1 < len; i++) { // 枚举起始端点
int j = i + L -1; // 右端点
if(S[i] == S[j] && dp[i+1][j-1] == 1) {
dp[i][j] = 1;
ans = L; // 更新
}
}
}
printf("%d\n", ans);
return 0;
}