题目描述
https://leetcode-cn.com/problems/longest-palindromic-substring/
思路
利用动态规划。
当前状态为 dp[i][j],表示字符串第 i 个字符和第 j 个字符之间是否是回文串(i<=j)。当前状态与前一个状态 dp[i+1][j-1] 有关,如果 dp[i+1][j-1] 是回文串,且 s[i]==s[j] ,则 dp[i][j] 也是回文串。
初始条件如下:单个字符一定是回文串,两个相邻字符如果相等,也能构成回文串。然后多次遍历字符串,第一次遍历先得到所有长度为3的字符串是否是回文串,再根据短字符串判断稍长的字符串是否是回文串。
如果字符串长度为Max,那么dp数组的大小应该设置为Max行Max列。但dp数组有一半的空间没有用,因为i>j是无意义的。
本题中利用的是二维数组存放回文串头尾index,空间复杂度是 O(n^2)。
实现
class Solution {
public:
string longestPalindrome(string s) {
int Max = s.length();
if(Max==0)
return "";
bool dp[Max][Max];
memset(dp,false,sizeof(dp));//
int longest = 1, posl=0;
for(int i=0; i<Max; i++)
dp[i][i]=true;//单个字符组成的字符串一定是回文串
for(int i=0; i<Max-1; i++)
if(s[i]==s[i+1])
{
dp[i][i+1]=true;//相邻字符相等
longest = 2;
posl = i;
}
for(int n=3; n<=Max; n++)
{
for(int i=0, j=i+n-1; j<Max; i++,j++)
{
if(dp[i+1][j-1] && s[i]==s[j])
{
dp[i][j]=true;
longest = n;
posl = i;
}
}
}
string res(s,posl,longest);
return res;
}
};
动态规划的dp数组一般情况下都是可以简化的,也就是说duck不必记录那么多数据,有些数据用过一次就不会再用,可以直接被后来的数据覆盖掉。
本题中,要判断长度为 L 的字符串是否是回文串时,就需要知道长度为 (L-2) 字符串是否是回文串,而对于长度为 (L+1) 的字符串,则需要知道 (L-1) 字符串的信息。所以我们只需要保留 2 行dp数组,用于记录前一次和前前次遍历生成的信息。这样,空间复杂度可以降低为 O(n) 。
class Solution {
public:
string longestPalindrome(string s) {
int Max = s.length();
if(Max==0)
return "";
if(Max==1)
return s;
bool dp[2][Max];
memset(dp,true,sizeof(dp)/2);//单个字符一定是回文的
memset(dp+1,false,sizeof(dp)/2);//相邻字符不一定
int longest = 1, posl=0;
for(int i=0; i<Max-1; i++)
if(s[i]==s[i+1])
{
dp[1][i]=true;//如果相邻字符相等
longest = 2;
posl = i;
}
for(int n=3; n<=Max; n++)
{
for(int i=0, j=i+n-1; j<Max; i++,j++)
{
if(dp[(n-1)%2][i+1] && s[i]==s[j])
{
dp[(n-1)%2][i]=true;
longest = n;
posl = i;
continue;
}
dp[(n-1)%2][i]=false;
}
}
string res(s,posl,longest);
return res;
}
};
如果长度为 L-2 的所有字符串都不是回文串,那么 L 的字符串就不可能是回文串。利用这个信息,可以减小算法的平均时间复杂度。但算法的时间复杂度不变,仍为 O(n^2) 。
class Solution {
public:
string longestPalindrome(string s) {
int Max = s.length();
if(Max==0)
return "";
if(Max==1)
return s;
bool dp[2][Max];
memset(dp,true,sizeof(dp)/2);//单个字符一定是回文的
memset(dp+1,false,sizeof(dp)/2);//相邻字符不一定
int longest = 1, posl=0;
for(int i=0; i<Max-1; i++)
if(s[i]==s[i+1])
{
dp[1][i]=true;//如果相邻字符不相等
longest = 2;
posl = i;
}
//allfalse用来判断长度为n的字符串是否都不是回文串,如果为真,就不用再判断长度为n+2的字符串了。
//isbreak用来控制当前循环提前结束。
bool allfalse[2]={true,true}, isbreak[2]={false,false};
for(int n=3; n<=Max; n++)
{
if(isbreak[0] && isbreak[1])
break;
if(isbreak[(n-1)%2])
continue;
for(int i=0, j=i+n-1; j<Max; i++,j++)
{
if(dp[(n-1)%2][i+1] && s[i]==s[j])
{
allfalse[(n-1)%2]=false;
dp[(n-1)%2][i]=true;
longest = n;
posl = i;
continue;
}
dp[(n-1)%2][i]=false;
}
if(!allfalse[(n-1)%2]) allfalse[(n-1)%2]=true;
else isbreak[(n-1)%2] = true;
}
string res(s,posl,longest);
return res;
}
};
其他算法
Manacher’s Algorithm,见解法5,时间复杂度可以降低到O(n)。
中心扩展算法,见解法4,时间复杂度O(n^2),是动态规划外的另外一种思路。