一、概述
输入一个字符串,输出它的最长回文子串。
经典DP问题。为什么突然想要做DP了呢?因为最近这几次打Contest,每次第三题和第四题必定是DP,不用DP做不出来那种。所以就自闭了。有的是自己写了半天一直TLE才后知后觉需要DP。因此痛定思痛,要开始学一下DP。拿之前没做过的DP题目来练手。因为是新学习的,所以就先不纠结时空复杂度了,能做得出来就好。
二、分析
最愚蠢的方法就是先看第一个,然后从第一个开始看到最后一个,找出最长的;然后从第二个开始看到最后,需要O(n^3)的时间。使用DP则仅需要O(n^2)的时间。那么如何使用DP呢?
DP在思考上的核心是找到递推公式,也就是我现在已经知道一个样例是对的了,那么在这个样例上加上什么条件可以得到下一个样例呢?以此题为例,假设我们已经知道字符串的s[3,5]是一个回文串,那么加上什么条件可以得到下一个回文串?看字符串的2和6,如果s[2]==s[6],那么就找到了下一个回文串。
也就是说,想知道s[i,j]是不是回文串,我们需要知道s[i+1,j-1]是不是,同时要判断s[i]和s[j]是否相等。
递归公式需要给出前两项,因此我们需要先手动算出长度为1和长度为2的所有子串是否为回文串,然后其余长度的在后面递推就可以得到了。如下图所示:
DP在实现上的核心是如何通过s[i,j]找到s[i+1,j-1]。也就是如何实现递推关系。本题所使用的是二维DP表,以babad为例,写下来DP表如下:
从上图中我们可以很轻松的得到递推关系的实现:
dp[i][j]=(dp[i-2][j+1])&&(s[j]==s[j+i]);
注意这里,i+1是子串长度,j是子串最左端元素的下标。
从而整体代码如下:
class Solution {
public:
string longestPalindrome(string s) {
int dp[1010][1010]={0};
if(s.size()==0)
{
return "";
}
int max_i,max_j;
for(int i=0;i<s.size();++i)
{
int flag=0;
for(int j=0;j<s.size()-i;++j)
{
if(i<2)
{
dp[i][j]=(s[j]==s[j+i]);
if(dp[i][j]==1&&flag==0)
{
max_i=i;
max_j=j;
flag=1;
}
}
else
{
dp[i][j]=(dp[i-2][j+1])&&(s[j]==s[j+i]);
if(dp[i][j]==1&&flag==0)
{
max_i=i;
max_j=j;
flag=1;
}
}
}
}
return s.substr(max_j,max_i+1);
}
};
三、总结
最长回文子串还有更好的Manacher算法,我的算法也还有很大的优化空间。但是这是最简单最直观的一种实现了。优化的的问题,等到我对DP熟稔之后再说也不迟。DP给我的感觉,emmmm,类似数学归纳法?先是写好最基本的情况,然后一层一层递归,得到一般的情况。有一说一,当时高中我数学归纳法就不怎么样。