马拉车算法一般用于求最长回文子串的问题,暴力求解最长回文字串的时间复杂度是O(n^2),马拉车就是分析回文子串的特点,减少了暴力的过程,时间复杂度是O(n)。
最长回文子串问题
给一个字符串,求最长回文子串的长度。
暴力解法:
暴力解法的思路就是遍历每一个元素,以该元素为中心,不断向外扩充,直到不能扩充为止,遍历完所有的字符得到最大的。
#include<iostream>
#include<string>
using namespace std;
int main()
{
string str;
while(cin >> str)
{
int len = str.size();
int maxLen = 0;
for(int i=1; i<len; i++)
{
//当回文串为偶数个时
int low = i-1, high = i;
while(low>=0 && high<len && str[low]==str[high])
{
low--;
high++;
}
if(high-low-1>maxLen)
maxLen = high-low-1;
//当回文串为奇数个时
low=i-1; high=i+1;
while(low>=0 && high<len && str[low]==str[high])
{
low--;
high++;
}
if(high-low-1>maxLen)
maxLen = high-low-1;
}
cout << maxLen << endl;
}
return 0;
}
动态规划解法
- dp[i][i]也就是长度为1肯定是回文
- dp[i][i+1]也就是长度为2只有在s[i]==s[i+1]时才是回文
- 之后从长度为3开始已知检索下去
class Solution {
public:
string longestPalindrome(string s) {
int len=s.size();
if(len==0||len==1)
return s;
int start=0;//回文串起始位置
int max=1;//回文串最大长度
vector<vector<int>> dp(len,vector<int>(len));//定义二维动态数组
for(int i=0;i<len;i++)//初始化状态
{
dp[i][i]=1;
if(i<len-1&&s[i]==s[i+1])
{
dp[i][i+1]=1;
max=2;
start=i;
}
}
for(int l=3;l<=len;l++)//l表示检索的子串长度,等于3表示先检索长度为3的子串
{
for(int i=0;i+l-1<len;i++)
{
int j=l+i-1;//终止字符位置
if(s[i]==s[j]&&dp[i+1][j-1]==1)//状态转移
{
dp[i][j]=1;
start=i;
max=l;
}
}
}
return s.substr(start,max);//获取最长回文子串
}
};
马拉车解法:
马拉车算法涉及一些变量,首先是maxRight代表的是目前的最大右边界,pos是可以达到最大右边界的中心所在的位置,RL是回文半径数组,记录的是每一个中心点的回文半径长度,maxLength回文子串的最大长度。
一次遍历,每次遍历首先判断当前 i 是不是在maxRight的内部,也就是i < maxRight,如果满足,则RL[i] = min(RL[2 * pos - i], maxRight - i);第一个参数的意思是与 i 关于pos对称的另外一点的半径,第二个参数是 i 到最大右边界的距离,最大右边界之外的元素需要暴力扩充才能知道。
- 用#填充字符串,例如 ab 变成 #a#b# ,这样就不用讨论字符串个数为奇数或偶数的情况了
- p数组代表回文半径数组,初始为0,id代表当前达到最大右边界的中心位置,mx表达最大右边界的位置,index表示最长回文子串的中心索引位置
- 从j = 1开始遍历,对于每一个j ,首先判断最大右边界和 j 的位置,若 j 在右边界里面,就找到 j 关于 id 对应的点,求得对应点的回文半径和mx - j长度的较小值,将该值赋给p[ j ],若 j 在最大右边界 mx 外面,则直接将p[ j ]=1。之后只能暴力扩充来判断p[ j ]。
- 然后更新mx和最大回文长度
- 通过在首字符前加一个¥,结尾后面再加一个@,可以推出最长回文子串在原串中的位置(index-maxlength)/2,长度为maxlength-1
class Solution {
public:
string longestPalindrome(string s) {
//预处理源串
string t = "$#";
for(int i=0; i<s.size(); i++){
t += '#' + s[i];
}
//新建p数组,大小和t串一致,初始化为0 ,p[i]表示以t[i]为中心的回文串半径
vector<int> p(t.size() , 0);
//设定重要参数 mx(某回文串延伸到的最右边下标),id(mx所属回文串中心下标),
//reCenter(结果最大回文串中心下标),reLen(最大长回文长度)
int mx = 0, id = 0, reCenter = 0, reLen = 0;
//遍历t字符串
for(int i=1; i<t.size(); i++){
//核心算法
p[i] = mx > i ? min(mx - i , p[2*id - i]) : 1;
//上面的语句只能确定i~mx的回文情况,至于mx之后的部分是否对称,就只能老老实实去匹配了,匹配一个p[i]++
while(t[i + p[i]] == t[i - p[i]]) p[i]++;
//当t[i]匹配的 右边界超过mx时mx和id就更新
if(i+p[i] > mx){
mx = i+p[i];
id = i;
}
//更新结果数据
if(p[i] > reLen){
reLen = p[i];
reCenter = i;
}
}
return s.substr((reCenter - reLen) / 2 , reLen - 1) ;
}
};