失踪人口再次回归,最近闲来无事准备重新刷一下算法题,一来锻炼一下脑子,二来看看这几年学的算法分析类课程还记得多少,准备从LeetCode第一题开始,期间记录一下自己觉得有意思或者是不会的题目
第五题是求一个字符串的最长回文子串,我一开始的想法是DP,用一个二维数组dp[i][j]来标记从下标i到j是否是一个回文串。然后对于每一种可能的i,j 对,根据状态转移方程 dp(i, j) = dp(i+1, j-1) and s[i+1] == s[j-1] 来决定值。之后寻找最长值为true的i, j对就可以。但是很明显这个时间复杂度是。 然后看了一下题解,发现了一个线性算法
我理解的这个算法思想是这样的
首先他通过在每个字符之间插入一个#,并在开头和结尾分别放入^和$从而保证了每个字母都是处于奇数位置,从而将原题目中可能出现的奇偶两种可能统一到了一种算法中。
其中原来的奇数回文串的中心还是原来的字母,而偶数的中心则成为了#.
同时这样也简化了比较的过程,只需要每次都是比较两端是否相等,并统计长度,最后的长度即为原始字符串中对应的回文串长度。
这个过程有个很生动形象的网站http://manacher-viz.s3-website-us-east-1.amazonaws.com/#/
在以上基础上,这个算法利用了一个事实:假设有个已知的回文串s,从下标L开始,到R结束,回文串的中心点在C。那么C左右两侧的字符串是完全相等的(在回文串的意义上)。也就是说如果L-C之间有个子回文串,那么对应的在C-R的位置上一定有个一样长度的镜像回文串。
利用这个本质,我们对于一个位置的点i。则有2种可能,i<R, i>=R.
i<R:
对于这种情况,又有两种可能,可能性A:首先是他关于C镜像位置上的回文串长度 + i <R。代表在这个回文串在这个大的回文串内已经结束了,那么i的值,一定是等于镜像位置上的值。
可能性B:另一种可能是i关于C镜像位置上的回文串长度 + i >= R。那么我们不确定R右侧是否还有相同的字母,那么我们需要继续寻找直到不相等之后
以刚才的网站上的字符串为例:
对于情况A:i = 10, 镜像位置是6.
因为镜像位置上的值是1 + 10 < R = 15的,所以说明这个是确定10位置的值是镜像上的值1.因为我们已经遍历过6号位置的回文串,知道长度1之后就是不一样的字母了,因为镜像相等,所以10号位置的回文串最长就是1.
但是情况B:i=12, 镜像位置是4
但是,因为镜像位置的值是3,12+3 = 15 已经位于右侧的边界了。所以我们无法保证R之后的位置是否还能构成回文串,因为以C为中心的回文串并没有判断这一部分,所以我们要在镜像的值3的基础上继续向右侧遍历。直到遇到不一样的。同时B情况之后要以i为新的中心来做后面的判断
而对于另一种可能就是i不在L-R的范围内,那么说明i周围的信息是未知的,我们要从i开始挨个判断,思路跟B情况基本一致。这个也要更新新的中心点C。
这样走完一次之后,就将所有的回文串找出来了,然后就判断一下最长的返回就行了。
代码:
class Solution:
def longestPalindrome(self, s: str) -> str:
# intert a symbol to guarantee get a odd length string
oddS = "^"
for i in s:
oddS += "#"
oddS += i
oddS += "#$"
c = 0 # known palindromic center index
r = 0 # right range
i = 1 # current undetermind palindromic center. Start from first "#" symbol
ans = [0]*len(oddS)
maxAns = 0
ansCenter = -1
while i < len(oddS)-1:
# there are three possible situation
# First is current index i is on or out the range of r
if i >= r:
l = i - 1
r = i + 1
counter = 0
# since the start and end denoted by special character.
# When traverse to start or end it will must stop
# thus we do not need to condsider the boundary
while oddS[l] == oddS[r]:
l -= 1
r += 1
counter += 1
c = i
ans[c] = counter
r -= 1 # set the r boundary to the last same character
else:
# if current index i in the range
# since current index in a bigger palindromic string which centered at c
# we can ensure that the mirror index mi of i about c has same pattern
# then there comes two sub-situation
# one is the mirror index mi's length + index i is still in center's range r
# that means ans[mi] is the answer of i
if i + ans[c-(i-c)] < r:
ans[i] = ans[c*2-i]
else:
# else means i possible have more palidromic character out the range r
# we need to traverse out the range r
counter = r - i # that because we could not guarantee out boundary r
# the ramain part still same as inside of palindrome
l = i*2 -r -1
r = r + 1
while oddS[l] == oddS[r]:
l -= 1
r += 1
counter += 1
# and update the new center
c = i
ans[c] = counter
r -= 1
if maxAns < ans[i]:
ansCenter = i
maxAns = ans[i]
i += 1
ansS = ""
l = ansCenter - maxAns
r = ansCenter + maxAns
while l <= r:
if not oddS[l] == '#':
ansS += oddS[l]
l += 1
return ansS