python最長(zhǎng)回文串算法
給定一個(gè)字符串,要求在這個(gè)字符串中找到符合回文性質(zhì)的最長(zhǎng)子串。所謂回文性是指諸如 “aba”,"ababa","abba"這類的字符串,當(dāng)然單個(gè)字符以及兩個(gè)相鄰相同字符也滿足回文性質(zhì)。
看到這個(gè)問題,最先想到的解決方法自然是暴力枚舉,通過枚舉字符串所有字串的起點(diǎn),逐一判斷滿足回文性的子串,記錄長(zhǎng)度并更新最長(zhǎng)長(zhǎng)度。顯然這種算法的時(shí)間復(fù)雜度是很高的,最壞情況可以達(dá)到O(N*N)。所以呢,這里提出一個(gè)優(yōu)化的方案,通過枚舉字符串子串的中心而不是起點(diǎn),向兩邊同時(shí)擴(kuò)散,依然是逐一判斷子串的回文性。這種優(yōu)化算法比之前的算法在最壞的情況下(即只有一種字符的字符串)效率會(huì)有很大程度的上升。
由上述的優(yōu)化方案,我們知道了枚舉中心要比枚舉起點(diǎn)效率要好,然而這并不是最優(yōu)的算法。由于枚舉中心的算法同時(shí)影響的是中心兩邊的字符,所以我們可以通過枚舉中心的左邊字符作為中心的子串的回文性判斷枚舉中心右邊的字符作為中心得子串的回文性,這就是manacher算法。
manacher算法思想非常巧妙,首先遍歷字符串,假設(shè) i 為枚舉中心,則 j (j<i) 為中心的最長(zhǎng)回文子串長(zhǎng)度發(fā)f[j] 便已經(jīng)求出,此時(shí) j 的影響范圍便是[j-f[j]/2,j+f [j]] 。為了使左邊的字符 j 對(duì)枚舉中心右邊的影響最大,需要使 j+f[j]/2 最大。找到滿足j+f[j]/2最大的 j 之后,若 i 在[j,j+f[j]/2]中,則分兩種情況:
1 . i 關(guān)于 j 對(duì)稱的字符i'的影響范圍完全包含在j的影響范圍內(nèi),則由于回文性,i 的影響范圍大于等于i'的影響范圍,即f[i]>=f[i']
2. i 關(guān)于 j 對(duì)稱的字符i'的影響范圍不完全包含在j的影響范圍內(nèi),此時(shí)i的右側(cè)影響范圍大于等于[j-f[j]/2,i'],即i+f[i]/2>=i'-j+f[j]/2
由于對(duì)稱性,可得i+i" = 2*j。因此第一種情況下,f[i]>=f[2*j-i];第二種情況下,f[i]>=f[j]+2*j-2*i。
綜上1,2,可得f[i]>=min(f[2*j-i],f[j]+2*j-2*i)。由于i右邊存在未遍歷的字符,因此在此基礎(chǔ)上,繼續(xù)向兩邊擴(kuò)展,直到找到最長(zhǎng)的回文子串。
若i依然在j+f[j]/2后面,則表示i沒有被前面的字符的影響,只能逐一的向兩邊擴(kuò)展。
這個(gè)算法由于只需遍歷一遍字符串,擴(kuò)展的次數(shù)也是有限的,所以時(shí)間復(fù)雜度可以達(dá)到O(N)。
下面是Pthon3的程序,為了檢測(cè)算法的效率,依然提供最初的暴力枚舉算法作為最壞算法的參照。
python代碼:
#求最長(zhǎng)回文串類
class LPS:
#初始化,需要提供一個(gè)字符串
def __init__(self,string):
self.string = string
self.lens = len(self.string)
#暴力枚舉:作為算法效率參照
def brute_force(self):
maxcount = 0
for j in range(self.lens):
for k in range(j,self.lens):
count = 0
l,m = j,k
while m>=l:
if self.string[l]==self.string[m]:
l,m = l+1,m-1
else:
break
if m<l:
count = k-j+1
if count>maxcount :
maxcount = count
return maxcount
#優(yōu)化版:枚舉子串中心
def brute_force_opti(self):
maxcount = 0
if self.lens == 1: #只有一個(gè)字符直接返回1
return 1
for j in range(self.lens-1): #枚舉中心
count,u = 1,j
#對(duì)于奇數(shù)子串,直接擴(kuò)展
for k in range(1,j+1): #兩邊擴(kuò)展
l,m = u+k,j-k
if (m>=0)&(l<self.lens):
if(self.string[l]==self.string[m]):
count += 2
else:
break
if count>maxcount : #更新回文子串最長(zhǎng)長(zhǎng)度
maxcount = count
if self.string[j]==self.string[j+1]: #處理偶數(shù)子串,將兩個(gè)相鄰相同元素作為整體
u,count= j+1,2
for k in range(1,j+1): #兩邊擴(kuò)展
l,m = u+k,j-k
if (m>=0)&(l<self.lens):
if(self.string[l]==self.string[m]):
count += 2
else:
break
if count>maxcount : #更新回文子串最長(zhǎng)長(zhǎng)度
maxcount = count
return maxcount
#manacher算法
def manacher(self):
s = '#'+'#'.join(self.string)+'#' #字符串處理,用特殊字符隔離字符串,方便處理偶數(shù)子串
lens = len(s)
f = [] #輔助列表:f[i]表示i作中心的最長(zhǎng)回文子串的長(zhǎng)度
maxj = 0 #記錄對(duì)i右邊影響最大的字符位置j
maxl = 0 #記錄j影響范圍的右邊界
maxd = 0 #記錄最長(zhǎng)的回文子串長(zhǎng)度
for i in range(lens): #遍歷字符串
if maxl>i:
count = min(maxl-i,int(f[2*maxj-i]/2)+1)#這里為了方便后續(xù)計(jì)算使用count,其表示當(dāng)前字符到其影響范圍的右邊界的距離
else :
count = 1
while i-count>=0 and i+count<lens and s[i-count]==s[i+count]:#兩邊擴(kuò)展
count +=1
if(i-1+count)>maxl: #更新影響范圍最大的字符j及其右邊界
maxl,maxj = i-1+count,i
f.append(count*2-1)
maxd = max(maxd,f[i]) #更新回文子串最長(zhǎng)長(zhǎng)度
return int((maxd+1)/2)-1 #去除特殊字符
通過上面的程序,使用字符串為長(zhǎng)度1000的純‘a(chǎn)'字符串作為樣例,經(jīng)過測(cè)試:
暴力枚舉:49.719844s
中心枚舉:0.334019s
manacher:0.008000s
由此可見,長(zhǎng)度為1000時(shí),暴力枚舉的耗時(shí)已經(jīng)無法忍受了,而相比而言,中心枚舉在效率上已經(jīng)有很大幅度的提升,最優(yōu)的manacher耗時(shí)則為更短。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Python實(shí)現(xiàn)批量提取word文件中文本框內(nèi)容
在日常的辦公中,有時(shí)需要提取多個(gè)word文件中的文字框的內(nèi)容,這篇文章主要為大家介紹了三種常見的方法來提取文本框的內(nèi)容,希望對(duì)大家有一定的幫助2024-02-02
基于Python實(shí)現(xiàn)自制CV剪貼板功能
云桌面的win10不能調(diào)出剪貼板,對(duì)于CV工程師來說十分不方便,所以這篇文章主要介紹了如何使用Python實(shí)現(xiàn)一個(gè)CV剪貼板,提升常用語(yǔ)句的復(fù)制粘貼效率,感興趣的可以了解下2024-02-02
python實(shí)現(xiàn)自動(dòng)打卡小程序
這篇文章主要為大家詳細(xì)介紹了python實(shí)現(xiàn)自動(dòng)打卡小程序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-03-03

