《算法笔记》读书记录DAY_56

CHAPTER_11  提高篇(5)——动态规划

11.5最长回文子串

回文串是一个正读和反读都一样的字符串,即是一个左右对称的串。

下面来看最长回文子串的问题。

题目:

给出一个字符串S,字母区分大小写,求S的最大回文子串的长度。

输入样例:

PATZJUJZTACCBCC

输出样例:

9       

//最长回文子串为ATZJUJZTA,长度为9

思路:

如果采用暴力的解法,枚举左右端点,然后判断区间[ i , j ]内的子串是否回文。这么做的时间复杂度为O(n^3),显然不是很优。

下面介绍动态规划的一种做法,时间复杂度为O(n^2)。

令dp[i][j]表示S[i]至S[j](0<=i<=j<S.size())所表示的子串是否是回文子串,是则为1,不是为0。这样根据S[i]是否等于S[j],可以把情况分为两类:

(1)若S[i]==S[j]。那么只要S[i+1]至S[j-1]是回文串,S[i]至S[j]就是回文串;如果S[i+1]至S[j-1]不是回文串,则S[i]至S[j]不是回文串。

(2)若S[i]!=S[j]。那么S[i]至S[j]一定不是回文串。

由此写出状态转移方程如下:

dp[i][j]=dp[i+1][j-1]\, \, \, \left ( S[i]==S[j] \right )

dp[i][j]=0\, \, \, \, \left ( S[i]!=S[j] \right )

边界: dp[i][i]=1,dp[i][i+1]=(S[i]==S[i+1])?1:0。

这里还有一个问题没有解决,那就是如何枚举递推?如果按照之前的dp做法从 i 和 j 由小到大开始枚举,就无法保证计算dp[i][j]时dp[i+1][j-1]已经计算完毕。因此如果递推得到整个dp数组将是一个难点。

首先需要明确的是,因为 i < = j ,整个二维数组dp其实只使用了右半部分,即只有右半部分元素的下标才能满足i < = j 。因此递推是为了将dp数组的右半部分全部按转移方程赋值,我们的遍历需要在数组斜对角线的右半部分进行。如下图:

通过边界的确定,我们先将所有的dp[i][i]和dp[i][i+1]计算出来了(正如上图两条斜实线所表示的)。我们需要从边界开始递推其他的数,在计算dp[i][j]时应该保证dp[i+1][j]和dp[i][j-1]已经计算完毕,也就是dp[i][j]的下面一个数和左边一个数已经计算完毕。为了达到这个目的,我们的 i 需要从大到小枚举,j 从小到大枚举。因为两条斜实线已经计算完毕,我们的枚举从图中虚线开始,按照 i 从len-3到0、j 从 i+2到 len-1的方式遍历完dp的右半部分。

上面说的有些抽象,其实有种更容易理解的方式,在前面的动态规划小节中,有的题目dp数组只有一维,要计算dp[i]只需知道dp[i-1],这样我们从边界由小到大枚举 i 一定能保证dp[i-1]已经计算完成。但如果要计算dp[i]需要知道的是dp[i+1],我们则需要从边界由大到小枚举 i 才能保证递推 。这种思路在二维数组也是一样,本题中计算dp[i][j]需要知道dp[i+1][j-1],我们需要在 i 方向上由大到小枚举,j 方向上由小到大枚举。本题还有一点特殊在于,边界是斜对角线并且我们只需要用到一半的dp空间,因此在枚举第二维度 j 时会受到第一维度 i 的影响。

参考代码:

#include<iostream>
#include<string>
using namespace std;

const int maxn=101;
int dp[maxn][maxn];

int main() {
	string s;
	int ans=-1;
	cin>>s;
	int len=s.size();
	//边界 
	for(int i=0;i<len;i++) {
		dp[i][i]=1;
		if(i<len-1) {
			dp[i][i+1]=(s[i]==s[i+1])?1:0;
		}
	}
	//状态转移方程
	for(int i=len-3;i>=0;i--) {
		for(int j=i+2;j<=len-1;j++) {
			if(s[i]!=s[j]) {
				dp[i][j]=0;
			}
			else {
				dp[i][j]=dp[i+1][j-1];
			}
		}
	}
	//找到最大长度回文串 
	for(int i=0;i<=len-1;i++) {
		for(int j=i;j<=len-1;j++) {
			if(dp[i][j]==1&&ans<(j-i))
				ans=j-i;
		}
	}
	cout<<ans+1<<endl;
	return 0;
}

除此之外,最长回文串问题还有其他解法。例如二分法+字符串hash的做法时间复杂度为O(nlogn),将在后续的12.1节中介绍;还有时间复杂度仅仅为O(n)的Manacher算法,这里不再阐述,有兴趣可以去搜索该算法。

猜你喜欢

转载自blog.csdn.net/jgsecurity/article/details/121290423