题目
给你一个字符串 s,找到 s 中最长的回文子串。
示例1:
输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。
示例2:
输入:s = “cbbd”
输出:“bb”
题目分析
回文串:通俗的讲就是正着读反着读都一样的字符串
子串:不同于子序列,子串是字符串连续的子集,子序列是子集
最长:满足前两个要求的最长回文子串
解法一:暴力算法
设字符串S,子串S[i…j],暴力枚举S[i…j]的所有组合并判断是否为回文串。
时间复杂度分析:首先暴力枚举i,j时间复杂度为O(n2),判断是否为回文串时间复杂度为O(n),所以时间复杂度为O(n^3)
空间复杂度为:O(1)
时间复杂度太高不建议写
解法二:动态规划
这里我们考虑优化回文串的判断,将回文串的判断优化到O(1)
动态规划的核心思想是从小的问题转移到大的问题,或者说记忆之前的操作的结果,让当前的操作利用之前的操作结果,是典型的空间换时间的方法
这里分析怎样利用之前的操作结果,我们设dp[i][j]为S[i…j]是否为回文串,
首先当i==j时 dp[i][j]==true
当i,j越界或者i>j时 dp[i][j]==false
然后对串进行判断
当s[i]!=s[j] dp[i][j]==false 很显然当头部和尾部字符不相等的时候s[i…j]不是回文串
当s[i]==s[j] dp[i][j]==dp[i+1]dp[j-1] 这里如果头尾字符相等,就需要判断中间字串是否是回文串,这里就利用到了之前操作的结果
代码如下:
--动态规划算法 时间复杂度O(n^2) 空间复杂度O(n^2)可优化
function longestPalindromeDP(str)
left = 1
right = 1
row = #str
col = #str
dp = {} --dp[i][j]表示str.sub(i,j)能否组成回文串
table.insert(dp,1)
--初始化
for i=1,row do
for j=1,col do
if(i==j) then
table.insert(dp,true)--单个字符肯定是回文串
else
table.insert(dp,false)
end
end
end
for j=2,col do
for i=1,j do
if(string.sub(str,i,i)~=string.sub(str,j,j)) then
dp[i*col+j] = false
else
if(j-i<3) then
dp[i*col+j] = true
else
dp[i*col+j] = dp[(i+1)*col+j-1]
end
end
if(dp[i*col+j]==true) then
if(j-i>right-left) then
left = i
right = j
end
end
end
end
return string.sub(str,left,right)
end
解法三:中心扩展算法
中心扩展算法优化 的是i,j的枚举,将O(n^2)优化到O(n)
在这里,我们不用枚举头和尾,我们枚举子串的中心,修改回文串判断算法,从中心向外扩展计算回文串最大长度
但是这里有问题,当字串长度为奇数时有一个中心,但是长度为偶数时有两个中心,设计回文串算法的时候要注意
代码如下:
--中心扩散算法 时间复杂度O(n^2) 空间复杂度O(1)
function expandAroundCenter(str,left,right)
while(left>=1 and right<=#str and string.sub(str,left,left)==string.sub(str,right,right)) do
left = left - 1
right = right + 1
end
return left+1,right-1
end
function longestPalindrome(str)
len = #str
left = 0
right = 0
for i=1,len do
left1,right1 = expandAroundCenter(str,i,i)
left2,right2 = expandAroundCenter(str,i,i+1)
if(right1-left1 >right-left) then
left=left1
right=right1
end
if(right2-left2 >right-left) then
left=left2
right=right2
end
end
return string.sub(str,left,right)
end
解法四:Manacher算法(马拉车算法)
这种算法结合了动态规划的算法与中心扩展算法,并结合了回文串特有的对称性质
1这里引入几个概念,臂长,最大中心点,最大右边界
臂长:回文串长度的一半
最大右边界:(已经遍历过的点的位置+该点臂长)的最大值
最大中心点:使得右边界最大的点
设最大右边界maxRight,最大中心点maxCenter,当前遍历的点的位置为i
这时如果按照中心扩展算法就会从i一个个的去扩展遍历,时间复杂度为O(n),但事实上我们可以用到我们之前操作的结果(动态规划的思想),
如果 i<maxRight,说明点i在最大中心点maxCenter的臂长范围内,这就有一个好处,根据回文串的对称性,我们可以根据i关于maxCenter的对称点来让优化计算i的臂长,为什么呢?
因为我们可以根据对称点的臂长(已经计算过)来让以i为中心的扩展少遍历一些重复的字符(对称性)
具体少多少呢答案为:min(对称点的臂长,maxRight到i的距离),这是因为我们只能保证在最大右边界内两边字符一定对称。
这里我们就完成了中心扩展算法的优化(跳过重复字符的判断,利用对称性和记忆化),但是有一个注意点就是这里只试用与奇数串(中心点只有一个,中心点有两个的太过复杂)
所以我们要对原串进行预处理,将其补充成奇数串
代码如下:
--Manacher算法(马拉车算法)是中心扩展算法和动态规划的结合版 时间复杂度O(n) 空间复杂度O(n)
function expendManacher(str,left,right)
while(left>=1 and right<=#str and string.sub(str,left,left)==string.sub(str,right,right)) do
left = left - 1
right = right + 1
end
return (right-left-2)//2
end
function min(numa,numb)
if(numa<numb) then
return numa
else
return numb
end
end
function longestPalindromeManacher(str)
--字符串预处理,插入特殊字符,将str的长度处理成奇数个
local newtable = {"#"}
for i=1,#str do
table.insert(newtable,string.sub(str,i,i))
table.insert(newtable,"#")
end
local newstr = table.concat(newtable)
arm_len = {}
local maxRight=0
local maxCenter=0
local left=1
local right=1
for i=1,#newstr do
local curr_arm_len = 0
if(i<maxRight) then
local mirror = maxCenter*2-i
local ignore_arm_len = min(arm_len[mirror],maxRight-i)
curr_arm_len = expendManacher(newstr,i-ignore_arm_len,i+ignore_arm_len)
else
curr_arm_len = expendManacher(newstr,i,i)
end
table.insert(arm_len,curr_arm_len)
if(i+curr_arm_len>maxRight) then
maxCenter=i
maxRight=i+curr_arm_len
end
if(curr_arm_len*2+1>right-left)then
left=i-curr_arm_len
right=i+curr_arm_len
end
end
anstable={}
for i=left,right do
if(string.sub(newstr,i,i)~="#") then
table.insert(anstable,string.sub(newstr,i,i))
end
end
return table.concat(anstable)
end
完整测试代码
a={"asddsa","axdvvdxaax","ababaabbaababa"}
--中心扩散算法 时间复杂度O(n^2) 空间复杂度O(1)
function expandAroundCenter(str,left,right)
while(left>=1 and right<=#str and string.sub(str,left,left)==string.sub(str,right,right)) do
left = left - 1
right = right + 1
end
return left+1,right-1
end
function longestPalindrome(str)
len = #str
left = 0
right = 0
for i=1,len do
left1,right1 = expandAroundCenter(str,i,i)
left2,right2 = expandAroundCenter(str,i,i+1)
if(right1-left1 >right-left) then
left=left1
right=right1
end
if(right2-left2 >right-left) then
left=left2
right=right2
end
end
return string.sub(str,left,right)
end
--动态规划算法 时间复杂度O(n^2) 空间复杂度O(n^2)可优化
function longestPalindromeDP(str)
left = 1
right = 1
row = #str
col = #str
dp = {} --dp[i][j]表示str.sub(i,j)能否组成回文串
table.insert(dp,1)
--初始化
for i=1,row do
for j=1,col do
if(i==j) then
table.insert(dp,true)--单个字符肯定是回文串
else
table.insert(dp,false)
end
end
end
for j=2,col do
for i=1,j do
if(string.sub(str,i,i)~=string.sub(str,j,j)) then
dp[i*col+j] = false
else
if(j-i<3) then
dp[i*col+j] = true
else
dp[i*col+j] = dp[(i+1)*col+j-1]
end
end
if(dp[i*col+j]==true) then
if(j-i>right-left) then
left = i
right = j
end
end
end
end
return string.sub(str,left,right)
end
--Manacher算法(马拉车算法)是中心扩展算法和动态规划的结合版 时间复杂度O(n) 空间复杂度O(n)
function expendManacher(str,left,right)
while(left>=1 and right<=#str and string.sub(str,left,left)==string.sub(str,right,right)) do
left = left - 1
right = right + 1
end
return (right-left-2)//2
end
function min(numa,numb)
if(numa<numb) then
return numa
else
return numb
end
end
function longestPalindromeManacher(str)
--字符串预处理,插入特殊字符,将str的长度处理成奇数个
local newtable = {"#"}
for i=1,#str do
table.insert(newtable,string.sub(str,i,i))
table.insert(newtable,"#")
end
local newstr = table.concat(newtable)
arm_len = {}
local maxRight=0
local maxCenter=0
local left=1
local right=1
for i=1,#newstr do
local curr_arm_len = 0
if(i<maxRight) then
local mirror = maxCenter*2-i
local ignore_arm_len = min(arm_len[mirror],maxRight-i)
curr_arm_len = expendManacher(newstr,i-ignore_arm_len,i+ignore_arm_len)
else
curr_arm_len = expendManacher(newstr,i,i)
end
table.insert(arm_len,curr_arm_len)
if(i+curr_arm_len>maxRight) then
maxCenter=i
maxRight=i+curr_arm_len
end
if(curr_arm_len*2+1>right-left)then
left=i-curr_arm_len
right=i+curr_arm_len
end
end
anstable={}
for i=left,right do
if(string.sub(newstr,i,i)~="#") then
table.insert(anstable,string.sub(newstr,i,i))
end
end
return table.concat(anstable)
end
for i=1,#a do
print("中心扩展",longestPalindrome(a[i]))
print("动态规划",longestPalindromeDP(a[i]))
print("Manacher算法",longestPalindromeManacher(a[i]))
print()
end