给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 1:
输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。
示例 2:
输入: “cbbd”
输出: “bb”
链接:leetcode原题
思路分析:
这题是区间dp经典题目。
先来看看回文子串的定义:
长度为1:a, b. 单个字符就是一个回文串
长度为2:aa, bb. 两个相同的字符就是一个回文串
长度为3:aba, dad.
长度为4:abba, daad.
长度为5:abcba, dacad.
我们可以定义两个状态i, j表示区间[i,j]. 令dp[i][j]表示区间[i,j]是否是回文子串。
可以发现,dp初始状态在长度为1,2的条件。当长度>2时,若s[i] == s[j],则dp[i][j] = dp[i+1][j-1]. 也就是说,只要s[i] == s[j],并且区间[i+1,j-1]是回文串,那么区间[i,j]就是回文串。比如aba,s[0] == s[2] == ‘a’,中间的b(dp[1][1])是回文串,于是aba是回文串。又比如abba,s[0] == s[3],dp[1][2]是回文串,那么abba就是回文串。
那么我们就可以验证该状态转移方程是正确的了。
接下来就是写递推。我们需要初始化长度1,长度2的回文串,长度3的回文串由长度1推出,长度4的回文串由长度2推出,长度5的回文串由长度3推出…
于是第一层for循环是长度[3,n]递推。
那么第二层for循环便是遍历区间的起点。
区间的终点 = 区间起点+区间长度-1.
两层for循环,时间复杂度O(n^2).
由于长度是递增的递推,因此我们只需要在满足回文串的条件下更新最大长度maxLen,就可以得到最长的回文子串长度。
下面上代码:
class Solution {
public:
int dp[1010][1010];
string longestPalindrome(string s) {
memset(dp,0,sizeof(dp));
int n = s.length();
int l = 0, maxLen = 1;
for(int i = 0;i < s.length();i++) //初始化
{
dp[i][i] = 1; //长度为1,自身就是回文串
if(s[i] == s[i+1] && i+1 < n) //两个相同字符组成长度为2的回文串
dp[i][i+1] = 2, l = i, maxLen = 2;
}
for(int len = 3;len <= n;len++) //区间长度递推
{
for(int i = 0;i+len-1 < n;i++) //区间起点枚举
{
int j = i+len-1; //区间终点
if(s[i] == s[j] && dp[i+1][j-1]) //满足回文串条件
{
dp[i][j] = 1; //定义区间[i,j]为回文串
l = i, maxLen = len; //更新区间起点和长度
}
}
}
return s.substr(l,maxLen);
}
};