目录
一、问题描述
给你一个字符串 s,找到 s 中最长的回文子串。
示例 1:
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
示例 2:
输入:s = "cbbd"
输出:"bb"
示例 3:
输入:s = "a"
输出:"a"
示例 4:
输入:s = "ac"
输出:"a"
限制:
1 <= s.length <= 1000
s 仅由数字和英文字母(大写和/或小写)组成
二、思路
本题可用动态规划,原因在于:
一个长度大于2的回文串,去掉首尾的字符后,仍然是一个回文串。
例如对于字符串 “ababa”,如果我们已经知道“bab” 是回文串,那么 “ababa” 一定是回文串,这是因为它的首尾两个字母都是“a”。
根据这样的思路,我们就可以用动态规划的方法解决本题。我们用 P(i,j)表示字符串 s 的第 i 到 j 个字母组成的串(下文表示成 s[i:j])是否为回文串:
这里的「其它情况」包含两种可能性:
-
s[i, j] 本身不是一个回文串;
-
i >j,此时 s[i, j] 本身不合法。
接下来使用DP四步法:
1.确定状态
1.1 研究最后一步
最后一步:假如最长子串长度为N(N>2),那么一定有一个子串X长度为N-2,这个子串X为最长字串去掉首尾后的子串,也为回文串。1.2 化为子问题
判断源字符串的某一个子串是否为回文串
2.转移方程
f[i][j] = (s[i] == s[j]) && (f[i+1][j-1])
f[i][j]表示从第i个位置到第j个位置的子串是回文串。
首先判断这个字串的首尾是否相等。
如果不相等,则肯定不是回文串;
如果相等,且他去掉首尾的子串也是回文串,则他自己是回文串。
3.初始条件和边界情况
3.1 初始条件
if(s.size()<2)return s;
备注:如果字符串的长度为0,则无法进行判断,返回0即可;如果字符串的长度为1,那么它本身就是最长回文子串,返回其本身即可。
3.2 边界情况
如果j<i,则说明这个字串的右边界比左边界还要小,显然是不合理的。所以边界情况为:i>=j。
又因为每一个长度为1的子串肯定是回文串,
所以可以将对角线元素直接初始化为true。
4.计算顺序
三、代码
class Solution {
public:
string longestPalindrome(string s) {
//如果字符串长度为0或者1,则直接返回该字符串即可。
if(s.size()<2)
{
return s;
}
//字符串长度
int length = s.size();
//初始化DP数组,是一个二维数组
//其中f[i][j]表示起始位置为i,终止位置为j的子串是否为回文串
vector<vector<bool>> f(length,vector<bool>(length));
//维护一个目前最长回文串长度maxLength
int maxLength = 1;
//维护一个目前最长回文串的起始下标begin
int begin = 0;
//初始化对角线元素,也就是这个字符串中所有长度为1的子串
//初始值为true,因为长度为1的子串肯定是回文串
for(int i = 0;i<length;i++)
{
f[i][i] = true;
}
//开始进行动态规划递推
//顺序:先从最上行到对角线,再换列,重复上述操作。
for(int j = 1;j<length;j++)
{
//起始位置从0开始,终止位置为j
for(int i = 0;i<j;i++)
{
//如果左右两个字符都不想等,那么肯定不是回文串
if(s[i] != s[j])
{
f[i][j] = false;
}
//如果两端字符相等
else
{
//且字串长度小于3,也就是2,1,0,那么肯定为回文串
if(j-i<3)
{
f[i][j] = true;
}
//不然,采用DP递推式
else
{
f[i][j] = f[i+1][j-1];
}
}
//上面已经判断了f[i][j]是否为回文串,如果是,且长度比目前维护的最大长度回文子串还大,
//那么更新maxLength和begin即可
if(f[i][j] && (j-i+1)>maxLength)
{
maxLength = j-i+1;
begin = i;
}
}
}
return s.substr(begin,maxLength);
}
};