leetcode 5. 最长回文子串
问题描述
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 1:
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
示例 2:
输入: "cbbd"
输出: "bb"
代码1(动态规划)
使用动态规划,定义
\[dp[i][j] = \begin{cases}
1 \quad \text{ 以第i个元素开头第j个元素结尾的字符串是回文子串}\\
0 \quad \text{ 以第i个元素开头第j个元素结尾的字符串不是回文子串}
\end{cases}
\]
显然单个字符一定是回文子串,连着的两个字符如果相等也是回文子串,因此可以只考虑长度大于2的回文子串的情况。
class Solution {
public:
string longestPalindrome(string s) {
int n = s.size();
if(n == 0 || &s == NULL) return "";
if(n == 1) return s;
int L,i,j,k,start;
vector<vector<int>> dp(n,vector<int>(n,0));
//int dp[n][n] = {0};
//初始化边界
for(i = 0; i < n; i++)
{
dp[i][i] = 1;
start = i;
L = 1;
}
for(i = 1; i < n;i++)
{
if(s[i] == s[i-1])
{
dp[i-1][i] = 1;
start = i-1;//进行赋值,防止没有长度大于3的回文子串
L = 2;
}
}
//正式求解,初始化了长度为1和2的情况,只需要找大于2的情况
for(i = 3; i <= n; i++)//这里的i代表了长度
{
for(j = 0; j + i -1 < n; j++)
{
k = j+i-1;
if(s[k] == s[j] && dp[j+1][k-1] == 1)
{
start = j;
L = i;
dp[j][k] = 1;
}
}
}
return s.substr(start,L);
}
};
结果:
执行用时 :376 ms, 在所有 C++ 提交中击败了12.73%的用户
内存消耗 :186.6 MB, 在所有 C++ 提交中击败了6.59%的用户
我们用
int dp[n][n] = {0};
代替原先的
vector<vector<int>> dp(n,vector<int>(n,0));
后,速度提高了一倍:
执行用时 :176 ms, 在所有 C++ 提交中击败了41.51%的用户
内存消耗 :13.3 MB, 在所有 C++ 提交中击败了56.59%的用户
考虑如何将空间复杂度降为\(O(N)\).定义\(dp[i]=j\)以i个字符开头j个字符结尾的子串为回文子串,显然初始化有\(dp[i]=i\),单独一个字符必定是回文子串。
class Solution {
public:
string longestPalindrome(string s) {
int n = s.size();
if(n == 0 || &s == NULL) return "";
if(n == 1) return s;
int i,left,right,start,len;
int dp[n];
dp[n-1] = n-1;
for(i = 0; i < n; i++)
{
if(s[i] != s[i+1])
{
dp[i] = i;
}
else
{
dp[i] = i+1;
}
}
for(i = 2; i <= n; i++)
{
for(left = 0; left+i-1 < n; left++)
{
right = left + i - 1;
if(s[left]==s[right]&&(dp[left+1]==right-1))
{
dp[left] = right;
}
}
}
len = 0;
for(i = 0; i < n; i++)
{
if(dp[i]-i+1>len)
{
len = dp[i]-i+1;
start = i;
}
}
return s.substr(start,len);
}
};
但是只能求解99/103个问题,在遇到"ccc"时求解失败,留待后面考虑。
代码2(中心扩展算法)
回文的中心要区分单双。假如回文的中心为 双数,例如 abba,那么可以划分为 ab bb ba,对于n长度的字符串,这样的划分有 n-1 种。假如回文的中心为 单数,例如 abcd, 那么可以划分为 a b c d, 对于n长度的字符串,这样的划分有 n 种。对于 n 长度的字符串,我们其实不知道它的回文串中心倒底是单数还是双数,所以我们要对这两种情况都做遍历,也就是 n+(n-1) = 2n - 1.
class Solution {
public:
string longestPalindrome(string s) {
int n = s.size();
if(n == 0 || &s == NULL) return "";
if(n == 1) return s;
int start = 0,len = 1,i,left,right;
for(i = 0; i < n-1; i++)
{
if(s[i] == s[i+1])
check(s,i,i+1,start,len);
check(s,i,i,start,len);
}
return s.substr(start,len);
}
void check(string s,int left,int right,int &start,int &len)
{
int i, n = s.size(), step = 1;
while(left-step >= 0 && right+step < n)
{
if(s[left-step] == s[right+step])
++step;
else
break;
}
int wide = 2*step + right - left - 1;
if(wide > len)
{
start = left - step + 1;
len = wide;
}
}
};
结果:
执行用时 :44 ms, 在所有 C++ 提交中击败了71.80%的用户
内存消耗 :76 MB, 在所有 C++ 提交中击败了35.90%的用户
代码3(马拉车算法)
这是一个\(O(N)\)级别的算法,参见博客.