给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为1000。
示例 1:
输入: “babad”
输出: “bab”
注意: "aba"也是一个有效答案。示例 2:
输入: “cbbd”
输出: “bb”
方法一: 动态规划,O(n^2)
i 为左指针,j 为右指针。
dp[ i ][ j ]指:从i到j位置组成的字串是否回文,回文则1,否则0。
若str[ i ] = str[ j ],则dp[ i ][ j ]是否回文取决于dp[ i + 1][ j - 1]是否回文。
若str[ i ] != str[ j ],则dp[ i ][ j ]肯定不回文。
#include <string>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
class Solution {
public:
//O(n^2)动态规划
string longestPalindrome(string s) {
int i, j;
vector<vector<bool>> dp(s.size(), vector<bool>(s.size(), false));//保存i到j子串是否回文
int maxI = 0, maxJ = 0;//保存最大回文子串的两端位置i与j
int maxNums = 1;//保存最大回文子串的字符数
右指针先定位
for (j = 0; j < s.size(); ++j)
{
dp[j][j] = true;
//左指针基于右指针,往左移动,这样可以利用之前算出来的值(这里脑洞有点大)
for (i = j - 1; i >= 0; --i)
{
if (s[i] == s[j])//如果两端字符相等
{
if (j - i == 1 || dp[i + 1][j - 1] != false)//如果两端字符相邻 - 或者 - 两端字符内部的两端仍是相等字符
{
dp[i][j] = true;//从i到j位置的字串回文
if (maxNums < j - i + 1)//获取最大回文子串
{
maxNums = j - i + 1;
maxI = i;
maxJ = j;
}
}
else
dp[i][j] = false;
}
//如果两端字符不相等
else
dp[i][j] = false;//从i到j位置的字串不回文
}
}
return s.substr(maxI, maxJ - maxI + 1);
}
};
方法二: Manacher算法,O(n)
马拉车算法:【O(N),不断扩大R圈,直至最后一个字符】
用于计算字符串中最大的回文子串
添加虚位
计算回文半径数组
可能性:
- i 在当前 R 外面 =>> 暴力直接扩 R 【暴力循环比较判断,以 i 为中心进行回文判断】
- i 在当前 R 内(i’ 为 i 围绕当前中心 C 作的 [左边] 对称点)
- (1)i’ 位置的回文半径圈没有超过当前 R 圈=>> i 位置的回文半径等于 i’ 的回文半径 【O(1)】
- (2)i’ 位置的回文半径圈超过了当前 R 圈 =>> i 位置的回文半径等于 i 位置到当前 R 位置 【O(1)】
- (3)i’ 位置的回文半径圈正好位于当前R圈 =>> i 位置到 R 位圈位置不用检测,以后需要进行比较检测,扩大当前最长回文半径 R,修改当前最长回文中心 C 【暴力循环比较判断,以i为中心进行回文判断】
//马拉车算法
string longestPalindrome(string s) {
//结果变量
int resultCenter = 0;//结果的回文中心位置
int resultMaxRadius = 0;//结果的最大回文半径值
//添加虚位‘#’
string sInput(1, '#');
for (int i = 0; i < s.size(); ++i)
{
sInput += s[i];
sInput.append("#");
}
vector<int> radiusStr(sInput.size(), 0);//回文半径数组 【int radiusStr[sInput.size()] = { 0 };不出错】
int rightCenter = -1;//当前最右?回文有边界的最早出现的回文中心 C
int rightBorder = -1;//当前最右?回文有边界位置 R
//循环计算每个位置的回文半径
for (int i = 0; i < sInput.size(); ++i)
{
//当前位置在最右边界外面,进行暴力扩大判断当前位置的回文
if(i >= rightBorder)
{
int j = 1;
//暴力判断当前位置的回文
while(i + j < sInput.length() && i - j > -1)
{
if (sInput[i + j] == sInput[i - j])
++radiusStr[i];
else
break;
++j;
}
//判断是否修改最右边界位置值与中心值
//判断条件为最右边界是否被扩大
if(i + radiusStr[i] > rightBorder)
{
rightBorder = i + radiusStr[i];
rightCenter = i;
}
}else
{
int i_symmetry = rightCenter - (i - rightCenter);//i位置以rC为中心的对称点 => i'
if(i_symmetry >= 0)//对称点是存在的
{
int i_symmetry_radius = radiusStr[i_symmetry];//获取对称点的回文半径
//rightBorder - i:最右回文右边界到i的距离
//i_symmetry_radius:i对称点i’的回文半径
//判断两个距离哪个更大
if(i_symmetry_radius > rightBorder - i)
{
radiusStr[i] = rightBorder - i;
}else if(i_symmetry_radius < rightBorder - i)
{
radiusStr[i] = radiusStr[i_symmetry];
}else if(i_symmetry_radius == rightBorder - i)
{
//i到最右边界R之间无需判断
radiusStr[i] = i_symmetry_radius;
//从R+1开始暴力判断
int j = i_symmetry_radius + 1;
//暴力扩大
while (i + j < sInput.length() && i - j > -1)
{
if (sInput[i + j] == sInput[i - j])
++radiusStr[i];
else
break;
++j;
}
//判断是否修改最右边界位置值与中心值
//判断条件为最右边界是否被扩大
if (i + radiusStr[i] > rightBorder)
{
rightBorder = i + radiusStr[i];
rightCenter = i;
}
}
}
}
//每次循环都保存最大回文半径的中心位置
if(resultMaxRadius < radiusStr[i])
{
resultMaxRadius = radiusStr[i];
resultCenter = i;
}
}
//最后得出最长回文子串
return s.substr(resultCenter / 2 - resultMaxRadius / 2, resultMaxRadius);
}