Longest Palindromic Substring
1. Introduction
Longest palindromic substring(最长回文子串)问题可描述为: 如何在给定字符串中寻找最大长度的连续回文子串. 假设回文子串的正序为s1, 倒序为s2, 则s1=s2. 例如, bananas
的最长回文子串为anana
, apple
的最长回文子串为pp
.
2. Brute Force
最简单的实现方式是遍历所有字符, 将每个字符视为一个回文子串的中心, 然后以该中心点向外拓展, 直到不满足回文条件. 需要注意的是, 由于回文子串的长度可为偶数, 意味着中心点并不一定是单个字符, 因此需修改原字符串, 假设原字符串为book
, 长度为4, 修改后为#b#o#o#k#
, 长度为9; 假设原字符串长度为N
, 则修改后长度为2N+1
, 从而保证字符串的长度永远为奇数.
char[] preprocess(String s) { |
该算法的时间复杂度为O(N2)(原字符串的长度为N).
3. Manacher's Algorithm
Brute force的问题在于, 从左向右遍历字符时, 当前字符没有利用之前找到的回文子串信息. 假设原字符串为s
, 预处理后的字符串为S
, 若当前字符(S[i])位于之前某个字符(S[j], j<i)为中心点的回文子串范围内, 则可找到以S[j]为中心, S[i]的镜像字符S[i′].
palindrome⏞S[l]…Si′…Sj…Si…S[r]
介绍Manacher算法前, 需引入三个变量:
- 最右边界
r
: 已遍历的字符中, 其回文子串所能抵达的最大边界 - 中心点
c
: 最右边界r
所在的回文子串的中心点位置 - 数组
p
, 其包含每个字符向外拓展所得的回文子串的半径: 该数组长度为|S|, 每处理一个字符, 将其回文子串的半径放入p
中
以字符串book
为例, 预处理后S
为$#b#o#o#k#@
, S
的首字符为$
, 尾字符为@
, 这两个字符使得中心点向外拓展时无需检查边界. 数组p
与S
中每个字符的对应如下:
index | character in S | radius of palindromic substring |
---|---|---|
0 | $ | 0 |
1 | # | 0 |
2 | b | 1 |
3 | # | 0 |
4 | o | 1 |
5 | # | 2 |
6 | o | 1 |
7 | # | 0 |
8 | k | 1 |
9 | # | 0 |
10 | @ | 0 |
Manacher算法遍历字符时, 不处理$
和@
, 因此遍历范围为[1,|S|−1]. 假设当前遍历到坐标i
, 可能遇到以下情况:
3.1 i>r
若i>r, 则中心点c与S[i]的位置关系如下:
palindrome⏞S[l]…Sc…S[r]…S[i]
S[l]表示中心点c所在回文子串的左边界. 由于S[i]没有位于任何先前遍历字符所在回文子串内, 因此无法使用回文字符串的对称性推断S[i]的情况, 只能采用brute force方式向左右拓展, 直到字符不匹配, 并更新最右边界r和中心点c.
3.2 i≤r
若i≤r, 则S[i]以c为轴, 可以找到镜像坐标i′
(i−c=c−i′):
palindrome⏞S[l]…S[i′]…Sc…S[i]…S[r]
假设S[i′]的半径为k
, 存在以下几种情况:
i′−k>l
: S[i′]所在回文子串的左边界未超出c所在回文子串的左边界, 字符串如下:
palindrome⏞S[l]…S[i′−k]…S[i′]…S[i′+k]⏟palindrome…Sc…S[i]…S[r]上图可得出以下结论:
- 由于S[l:r]为回文字符串, 且S[i′−k:i′+k]为回文字符串, 因此S[i−k:i+k]为回文字符串
- 由于S[i′]所在的回文子串的半径为
k
, 因此S[i′−k−1]≠S[i′+k+1]=S[i−k−1] - 由于S[l:r]为回文字符串, 且i′−k>l, 因此S[i′−k−1]=S[i+k+1]
综上所述, S[i+k+1]=S[i′−k−1]≠S[i′+k+1]=S[i−k−1], 因此S[i]所在回文子串的半径为
k
.i′−k=l
: S[i′]所在回文子串的左边界等同c
所在回文子串的左边界, 字符串如下:
S[l−1]palindrome⏞S[l](S[i′−k])…S[i′]…S[i′+k]⏟palindrome…Sc…S[i]…S[r](S[i+k])S[r+1]上图可得出以下结论:
- 由于S[l:r]为回文字符串, 且S[i′−k:i′+k]为回文字符串, 因此S[i−k:i+k]为回文字符串
- 由于S[i′]所在回文子串的半径为
k
, 因此S[i′−k−1]≠S[i′+k+1]=S[i−k−1] - 由于l=i′−k−1, 因此S[i′−k−1]与S[i+k+1]的关系不确定
因此S[i−k−1]与S[i+k+1]的关系无法确定, 但S[i]所在回文子串的长度至少为
k
.i−k<l
: S[i′]所在回文子串的左边界超出c
所在回文子串的左边界, 字符串如下:
S[i′−k]…S[l]…S[i′]…S[i′+k]⏟palindromeS[i′−k]…palindrome⏞S[l]…S[i′]…S[i′+k]…Sc…S[i]…S[r]该情况与第二种情况相同: S[i]所在回文子串的半径至少为
k
.
4. Implementation of Manacher
char[] preprocess(String s) { |
上述代码中, Manacher()
返回数组p
, 若需要最长回文子串的长度, 可遍历p
并找到最大值; 若需要最长回文子串, 可调用以下函数:
String longestPalindromicSubstring(String s, int[] p) { |