对应letecode链接:
https://leetcode-cn.com/problems/longest-palindromic-substring/
题目描述:
给你一个字符串 s,找到 s 中最长的回文子串。
示例 1:
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
示例 2:输入:s = "cbbd"
输出:"bb"
示例 3:输入:s = "a"
输出:"a"
示例 4:输入:s = "ac"
输出:"a"
首先回文串有两种形式:
一种是奇数的比如
"aba"
,一种是偶数的比如"abba"
如果我们单个字符为中心向两边扩散在这种情况下就会出现问题长度为偶数的回文:"abba"
首先我们以首字符a为中心向两边扩散得得到的最长回文字串的长度为1也就是a.我们在已下一个字符b为中心括的时候长度也为1下一个b为中心括的时候长度也为1最终我们求到的最长长回文字串的长度为1.但实际上字符串本身就是回文字符串这是因为他的对称轴是在一个虚的位置。
Manacher为了解决这个问题会在每个字符之间都会插入一个特殊字符,并且两边也会插入,这个特殊字符要保证不能是原字符串中的字符,这样无论原来字符串长度是奇数还是偶数,添加之后长度都会变成奇数。例如:
“#a#b#a#"(长度为7)
"#a#b#b#a#(长度为9)
在这里所加入的特殊字符要不要求是原串中没有出现的字符呢?答案是并不一定。特殊字符是可以随意。这是为什么因为在括散的过程中因为不会出现特殊字符和原来字符串中的字符相比较的情况。特殊字符只是起到辅助作用不会影响到最终的结果
在这里我们引入一个变量叫回文半径,通过添加特殊字符,原来字符串长度无论是奇数还是偶数最终都会变为奇数,因为特殊字符的引用,改变之后的字符串的所有回文子串长度一定都是奇数。并且回文子串的第一个和最后一个字符一定是你添加的那个特殊字符。其实很好证明
如果原来回文子串的长度是奇数,通过中间插入特殊字符,特殊字符的个数必定是偶数,在加上两边的特殊字符,长度必然是奇数
如果原来回文子串的长度是偶数,通过中间插入特殊字符,特殊字符的个数必定是奇数,在加上两边的特殊字符,长度必然是奇数
因为添加特殊字符之后所有回文子串的长度都是奇数,我们定义回文子串最中间的那个字符到回文子串最左边或者最右边的长度叫回文半径。
比如字符串
"babad"
在添加特殊字符之后每个字符的回文半径:
搞懂了这个我们再来看一下最长回文子串该怎么求。在上面我们讲过中心扩散法,我们会以每一个字符(中间会过滤掉重复的)为中心往两边扩散,如果以当前字符为中心往两边扩散计算完的时候,到下一个字符在往两边扩散的时候还要重新计算,那么有没有一种方法不用重新计算,而利用之前计算的结果呢,答案是肯定的。
假设当前字符s[maxCenter]为回文中心的最大回文长度为是从left到maxRight如果我们想求以字符
s[i]
为回文中心的最大回文长度,我们只需要找到i
关于maxCenter
的对称点j
,看下j的回文长度,因为j
已经计算过了。
有以后三种情况:
1.i在maxRight的左边并且以i关于maxCenter的对称点j 为回文中心的回文区域在leftmaxRight之内。
2.i在maxRight的左边并且以i关于maxCenter的对称点j为回文中心区域最大回文长度的左边刚好到达left或者超过left
3.如果i在maxRight的右边
情况一:i在maxRight的左边并且以i关于maxCenter的对称点j 为回文中心的回文区域在leftmaxRight之内。
为什什么i对应的回文半径就等于j对应的回文半径呢?通过对称性可知以i的回文半径至少是以j为回文中心的半径为什不能在大了?证明:假设已j为回文中心区域左边界的前一个字符为x,右边界的后一个字符为y,已i为回文中心区域左边界的前一个字符为z,右边界的后一个字符为L。这时就有当时为什么已j为回文中心的区域没能括的更大了原因只可能是x!=y由对称关系可得z==y,x==L。所以我们可以推出z!=L证毕。
情况二:
i在maxRight的左边并且以i关于maxCenter的对称点j为回文中心区域最大回文长度的左边刚好到达left或者超过left。如果是超过left那么对应i对应的回文半径为maxRight-i,如果刚好到达left那么对应回文半径的答案至少为maxRight-i是否能够更大就要看maxRight-i的前一个字符和maxRight+i是否相等如果相等那么就能够更长
情况三:
3.如果i在maxRight的右边
在这种情况下我们无法利用之前计算的结果,此时我们只能暴力扩
对应代码:
class Solution { public: string longestPalindrome(string s) { int len = s.length(); if (len < 1) { return ""; } // 预处理 string s1; for (int i = 0; i < len; i++) { s1 += "#"; s1 += s[i]; } s1 += "#"; len = s1.length(); int MaxRight = 0; // 当前访问到的所有回文子串,所能触及的最右一个字符的下一个位置 int Center = 0; // MaxRight对应的回文串的对称轴所在的位置 int MaxRL = 0; // 最大回文串的回文半径 int MaxCenter = 0; // MaxRL对应的回文串的对称轴所在的位置 int* RL = new int[len]; // RL[i]表示以第i个字符为对称轴的回文串的回文半径 memset(RL, 0, len * sizeof(int)); for (int i = 0; i < len; i++) { RL[i]=MaxRL>i?min(RL[2*Center-i],MaxRL-i):1;//将上序三种情况合并 // 尝试扩展RL[i],注意处理边界 while (i - RL[i] >= 0 // 可以把RL[i]理解为左半径,即回文串的起始位不能小于0 && i + RL[i] < len // 同上,即回文串的结束位不能大于总长 && s1[i - RL[i]] == s1[i + RL[i]]// 回文串特性,左右扩展,判断字符串是否相同 ) { RL[i]++; } // 更新MaxRight, pos if (RL[i] + i > MaxRight) { MaxRight = RL[i] + i ; Center = i; } // 更新MaxRL, MaxPos if (MaxRL <RL[i]) { MaxRL = RL[i]; MaxCenter = i; } } return s.substr((MaxCenter-MaxRL+1) / 2, MaxRL - 1);//返回字串 } };