力扣第5题:最长回文字串
题目描述
给你一个字符串 s
,找到 s
中最长的回文子串。
示例
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
输入:s = "cbbd"
输出:"bb"
解法分析
这道题,是去年寒假实习面试字节跳动的原题,因此非常的重要,目前主要的思路主要有暴力破解法,中心极限法,动态规划法以及马拉车算法,因为马拉车算法,在面试和笔试中不做要求所以没做归纳
1:暴力破解法
//暴力解法
//判断是否是回文
bool isPadlindrome(string s)
{
int len=s.size();
for(int i=0;i<len;i++)
{
if(s[i]!=s[len-i-1])
{
return false;
}
}
return true;
}
//输出,时间超时,时间复杂度 O(n^3)
string longestPalindrome4(string s)
{
//定义要保存的字符串
string ans;
int maxLength=0;
int len=s.size();
for(int i=0;i<len;i++)
{
for(int j=i+1;j<=len;j++)
{
//定义临时的字串;
string subStr=s.substr(i,j-i+1);
if(isPadlindrome(subStr)&&subStr.size()>maxLength)
{
ans=s.substr(i,j-i+1);
int ansLen=ans.size();
maxLength=max(maxLength,ansLen);
}
}
}
return ans;
}
算法实现的思路将输入的字符串按照起点和长度的大小一个一个进行切分,根据回文首尾字符必定相等的原则,进行判断。算法时间复杂度为 O ( n 3 ) O(n^3) O(n3),在leetcode提供的编译器中很有可能会报出编译超时的错误,所以不太推荐。
2:中心扩散法
//中心扩散,从当前点慢慢向两边进行扩散
int expandAroundCenter(string s,int left,int right)
{
int leftPoint=left,rightPoint=right;
while(leftPoint>=0&&rightPoint<s.size()&&s[leftPoint]==s[rightPoint])
{
leftPoint--;
rightPoint++;
}
//回退到之前的,而不是现在的
return rightPoint-leftPoint-1;
}
string longestPalindrome6(string s)
{
if(s.size()<1)
{
return "";
}
int start=0,end=0;
for(int i=0;i<s.size();i++)
{
//对子串分成奇偶进行讨论
int len=expandAroundCenter(s,i,i);
int len2=expandAroundCenter(s,i,i+1);
//取这两种情况的最大值
int maxLen=max(len,len2);
if(maxLen>end-start+1)
{
start=i-(maxLen-1)/2;
end=i+maxLen/2;
}
}
string subStr=s.substr(start,end-start+1);
return subStr;
}
在解决本问题最简便的方法就是采用中心扩散的方法,根据回文串的两边一定是中心对称,即假设一个点,让其向两边进行扩散,当遇到值不相等的状态,即刻返回,这样的算法复杂度是 O ( n 2 ) O(n^2) O(n2)
3: 动态规划法
//动态规划算法
string longestPalindrome7(string s)
{
int len=s.size();
if(len<2)
{
return s;
}
int maxLen=1,begin=0;
//遵循的思想是 dp[i][j]=dp[i+1][j-1]&&(S[i]==S[j])
// 目标 b a b a d
// 坐标 0 1 2 3 4
//
// index 0 1 2 3 4 i
// 0 1 0 1 0 0
// 1 1 0 1 0
// 2 1 0 0
// 3 1 0
// 4 1
// j
//条件 j-1-(i+1)+1<2=>j-i<3
//j-i+1<4=>当s[i..j]的长度为2或则3时,不用检查字串是否构成回文
//dp[i][j]表示s[i][j]是否为回文串
//vector声明成数组的一种方式,包含个len个重复的元素,每个元素的值都是vector<int>(len)
vector<vector<int>>dp(len,vector<int>(len));
//初始化对角线的数据为true,因为dp[i][i]一定是回文
for(int i=0;i<len;i++)
{
dp[i][i]=true;
}
//利用表达式开始递推,注意递推的过程要按列递推
//首先枚举字串的长度
for(int subLens=2;subLens<=len;subLens++)
{
//枚举左边界
for(int i=0;i<len;i++)
{
//枚举右边界,根据 j-i+1=subLens可以得到
int j=subLens+i-1;
//如果出现越界则跳出
if(j>=len)
{
break;
}
//dp[i][j]=dp[i+1][j-1]&&s[i]==s[j]
//开始根据表达式填入对应的值
if(s[i]!=s[j])
{
dp[i][j]=false;
}
else{
//字符串的长度如果在1,2,3之间则一定是回文的
if(j-i+1<4)
{
dp[i][j]=true;
}
else{
dp[i][j]=dp[i+1][j-1];
}
}
//只要dp[i][j]==true,则就代表s[i][j]一定可以构成回文串
//只要i,j二者之间的长度大于最大长度,就记录更新回文长度和起始位置
if(dp[i][j]&&j-i+1>maxLen)
{
maxLen=j-i+1;
begin=i;
}
}
}
string ans=s.substr(begin,maxLen);
return ans;
}
解决此问题最通用的方法,其实是动态规划问题,动态规划问题的实质上就是充分利用题目设定的前置条件。我们都知道,当只有一个字符的时候,一定是构成回文串的,那么当前一状态是回文串,后一状态的首尾数值相等。那么不用多说就一定能构成。动态规划的目的就是维持那么一张二元数组表,用以表示数值的通过与否。最后的时间复杂度在 O ( n 2 ) O(n^2) O(n2)