LeetCode005__Longest Palindromic Substring
来源:互联网 发布:淘宝账期延长怎么解除 编辑:程序博客网 时间:2024/06/05 21:04
1、引言
还是国际惯例,先贴上leetcode上的原题:
Given a string S, find the longest palindromic substring in S. You may assume that the maximum length of S is 1000, and there exists one unique longest palindromic substring.
这是一道关于字符串的题目,找寻字符串中的最大回文子串,这让我回想到考研时候数据结构中最让我头疼的KMP算法,真好趁这道题目的机会重新温故一下字符串的应用,主要以字符串的匹配,以及最大回文串,最大回文子序列三个方面来实现。
因为回顾比较多,所以本文有些长,并且部分脱离了Leetcode范畴。
2、KMP算法
kmp算法又称“看毛片”算法,是一个效率非常高的字符串匹配算法。不过由于其难以理解,所以在很长的一段时间内一直没有搞懂。虽然网上有很多资料,但是鲜见好的博客能简单明了地将其讲清楚。在此,综合网上比较好的几个博客(参见最后),尽自己的努力争取将kmp算法思想和实现讲清楚。
kmp算法完成的任务是:给定两个字符串O和f,长度分别为n和m,判断f是否在O中出现,如果出现则返回出现的位置。常规方法是遍历a的每一个位置,然后从该位置开始和b进行匹配,但是这种方法的复杂度是O(nm)。kmp算法通过一个O(m)的预处理,使匹配的复杂度降为O(n+m)。(以上来源http://blog.csdn.net/yutianzuijin/article/details/11954939/)
在之前阅读相关博客和资料的时候,我一直不太明白,怎么确保匹配子串发生移动时,在最大小标情况下保证之前的元素与被匹配数组元素相同。
以下将对此进行解释,但愿自己能够说清楚吧。(实例来源http://www.matrix67.com/blog/archives/115)
假如,A=”abababaababacb”,B=”ababacb”,我们来看看KMP是怎么工作的。我们用两个指针i和j分别表示,A[i-j+ 1..i]与B[1..j]完全相等。也就是说,i是不断增加的,随着i的增加j相应地变化,且j满足以A[i]结尾的长度为j的字符串正好匹配B串的前 j个字符(j当然越大越好),现在需要检验A[i+1]和B[j+1]的关系。当A[i+1]=B[j+1]时,i和j各加一;什么时候j=m了,我们就说B是A的子串(B串已经整完了),并且可以根据这时的i值算出匹配的位置。当A[i+1]<>B[j+1],KMP的策略是调整j的位置(减小j值)使得A[i-j+1..i]与B[1..j]保持匹配且新的B[j+1]恰好与A[i+1]匹配(从而使得i和j能继续增加)。我们看一看当 i=j=5时的情况。
i = 1 2 3 4 5 6 7 8 9 ……A = a b a b a b a a b a b …B = a b a b a c bj = 1 2 3 4 5 6 7
第一次比对到i==5,因为i=6时候发现A与B元素不相同,故而开始进行回头重比较:
i = 1 2 3 4 5 6 7 8 9 ……A = a b a b a b a a b a b …B = a b a b a c bj = 1 2 3 4 5 6 7
这样问题来了,KMP算法采用什么机制或者说具体是如何进行比对保证j==4的时候B[5]前面的元素与A[7]之前的四个元素相同呢?
其实道理很简单,我们可以看到,新的j可以取多少与i无关,只与B串有关。我们完全可以预处理出这样一个数组P[ j ],表示当匹配到B数组的第j个字母而第j+1个字母不能匹配了时,新的j最大是多少。P[j]应该是所有满足B[ 1…P[ j ] ]=B[ j-P[ j ]+1 … j ]的最大值。
因此,代码可以理解为扫描字符串A,并更新可以匹配到B的什么位置。
于是,我们需要一个算法实现P数组的预处理过程。
伪代码如下:
p[1] <- 0;j <- 0;for i <- 2 to m do{ while j > 0 and B[j+1] ≠ B[i] do j <- P[j]; if B[j+1] = B[i] then j <- j+1; p[i] <- j;}
其实代码很简单,实现了对于B数组的一次自我匹配,但是其中用到了摊还思想,需要细细品味。
有了P数组,那么得到KMP整体算法就没那么难了,
j <- 0;for i <- 1 to m do{ while j > 0 and B[j+1] ≠ A[i] do j <- P[j]; if B[j+1] = A[i] then j <- j+1; if j = m then writeln('Pattern occurs with shift ',i-m); p[i] <- j;}
附上python代码:
class KMP(object): def kmpAlgorithmPre(self, a): p=[0]*len(a) i = 0 for j in range (1,len(a)-1): while (i>0 and a[i]!=a[j]): i = p[i] # print(i) if a[i] == a[j]: i = i+1 p[j] = i print(p) return p def kmpAlorithm(self,a,b): pre = self.kmpAlgorithmPre(b) m = -1 samesub=[] temp = [] for n in range(0,len(a)-1): while(m>0 and b[m+1]!=a[n]): temp = [] #print(m) m = pre[m] if b[m+1] == a[n]: m = m+1 temp.append(b[m]) if len(temp)>len(samesub): samesub = temp[:] if m ==len(b)-1: m = pre[m] return samesub
3、Manacher算法
此部分参考自:http://blog.csdn.net/dyx404514/article/details/42061017
下面介绍Manacher算法的原理与步骤。
首先,Manacher算法提供了一种巧妙地办法,将长度为奇数的回文串和长度为偶数的回文串一起考虑,具体做法是,在原字符串的每个相邻两个字符中间插入一个分隔符,同时在首尾也要添加一个分隔符,分隔符的要求是不在原串中出现,一般情况下可以用#号。下面举一个例子:
(1)Len数组简介与性质
Manacher算法用一个辅助数组Len[i]表示以字符T[i]为中心的最长回文字串的最右字符到T[i]的长度,比如以T[i]为中心的最长回文字串是T[l,r],那么Len[i]=r-i+1。
对于上面的例子,可以得出Len[i]数组为:
Len数组有一个性质,那就是Len[i]-1就是该回文子串在原字符串S中的长度,至于证明,首先在转换得到的字符串T中,所有的回文字串的长度都为奇数,那么对于以T[i]为中心的最长回文字串,其长度就为2*Len[i]-1,经过观察可知,T中所有的回文子串,其中分隔符的数量一定比其他字符的数量多1,也就是有Len[i]个分隔符,剩下Len[i]-1个字符来自原字符串,所以该回文串在原字符串中的长度就为Len[i]-1。
有了这个性质,那么原问题就转化为求所有的Len[i]。下面介绍如何在线性时间复杂度内求出所有的Len。
(2)Len数组的计算
首先从左往右依次计算Len[i],当计算Len[i]时,Len[j](0<<=j<< i)已经计算完毕。设P为之前计算中最长回文子串的右端点的最大值,并且设取得这个最大值的位置为po,分两种情况:
第一种情况:i<=P
那么找到i相对于po的对称位置,设为j,那么如果Len[j]<< P-i,如下图:
那么说明以j为中心的回文串一定在以po为中心的回文串的内部,且j和i关于位置po对称,由回文串的定义可知,一个回文串反过来还是一个回文串,所以以i为中心的回文串的长度至少和以j为中心的回文串一样,即Len[i] > = Len[j]。因为Len[j] < P-i,所以说i+Len[j]< P。由对称性可知Len[i]=Len[j]。
如果Len[j] >= P-i,由对称性,说明以i为中心的回文串可能会延伸到P之外,而大于P的部分我们还没有进行匹配,所以要从P+1位置开始一个一个进行匹配,直到发生失配,从而更新P和对应的po以及Len[i]
第二种情况: i>P
如果i比P还要大,说明对于中点为i的回文串还一点都没有匹配,这个时候,就只能老老实实地一个一个匹配了,匹配完成后要更新P的位置和对应的po以及Len[i]。
(3)时间复杂度分析
Manacher算法的时间复杂度分析和Z算法类似,因为算法只有遇到还没有匹配的位置时才进行匹配,已经匹配过的位置不再进行匹配,所以对于T字符串中的每一个位置,只进行一次匹配,所以Manacher算法的总体时间复杂度为O(n),其中n为T字符串的长度,由于T的长度事实上是S的两倍,所以时间复杂度依然是线性的。
下面是算法的实现,注意,为了避免更新P的时候导致越界,我们在字符串T的前增加一个特殊字符,比如说‘$’,所以算法中字符串是从1开始的。
伪代码如下:
Manacher(st[0...n], len){ mx <- 0; ans <- 0; po <- 0; for i <- 1 to len do{ if mx >i then L[i] <- min(mx-i,L[2*po-i]); else L[i] <- 1; while st[i-L[i]] = st[i+L[i]] do L[i] <- L[i]+1; if L[i] +i >mx then { mx <- L[i]+i; po<-i;} ans <- max(ans, L[i]); } return ans-1;}
附上python实现代码:
class manacherAlogrithm(object): def preDeal(self,str): s=list(str) for i in range(0,len(s)*2+1,2): s.insert(i,'#') return s def manacher(self,str): mx =0 ans = 0 po = 0 s = self.preDeal(str) L = [0]*len(s) s.append('@') for i in range(0,len(s)-2): if mx > i: L[i] = min(mx-i, L[2*po-i]) else : L[i] = 1 while s[i-L[i]] == s[i+L[i]]: L[i]= L[i]+1 if L[i]+i>mx: mx = L[i]+i po = i if ans < L[i]: sw = s[i-L[i]+1:i+L[i]-1] ans = L[i] sw = filter(lambda x:x!='#',sw) sw = ''.join(sw) return sw
送入Leetcode跑跑:
效果还行
附上LeetCode中最佳的代码,学习学习:
class Solution: def longestPalindrome(self, s): # Transform S into T. # For example, S = "abba", T = "^#a#b#b#a#$". # ^ and $ signs are sentinels appended to each end to avoid bounds checking T = '#'.join('^{}$'.format(s)) n = len(T) P = [0] * n C = R = 0 for i in range (1, n-1): P[i] = (R > i) and min(R - i, P[2*C - i]) # equals to i' = C - (i-C) # Attempt to expand palindrome centered at i while T[i + 1 + P[i]] == T[i - 1 - P[i]]: P[i] += 1 # If palindrome centered at i expand past R, # adjust center based on expanded palindrome. if i + P[i] > R: C, R = i, i + P[i] # Find the maximum element in P. maxLen, centerIndex = max((n, i) for i, n in enumerate(P)) return s[(centerIndex - maxLen)//2: (centerIndex + maxLen)//2]
跑了一下,也没提高多少跟帖子说的时间不一样(差距有些大),可能改了数据集吧,就不放结果了。
- LeetCode005__Longest Palindromic Substring
- LeetCode: Longest Palindromic Substring
- LeetCode Longest Palindromic Substring
- LeetCode: Longest Palindromic Substring
- [Leetcode] Longest Palindromic Substring
- Longest Palindromic substring
- [LeetCode] Longest Palindromic Substring
- LeetCode5:Longest Palindromic Substring
- Leetcode : Longest Palindromic Substring
- Longest Palindromic Substring
- Longest Palindromic Substring
- [LeetCode]Longest Palindromic Substring
- leetcode Longest Palindromic Substring
- Longest Palindromic Substring
- LeetCode-Longest Palindromic Substring
- Longest Palindromic Substring
- Longest Palindromic Substring
- [LeetCode] Longest Palindromic Substring
- 个人总结:创建快捷方式的两种方法
- Spring IoC学习笔记(一)
- Struts2配置web.xml
- window.load和$(documen).ready()区别
- 区块链开发(三)编写调试第一个以太坊智能合约
- LeetCode005__Longest Palindromic Substring
- Java深入 - Java 内存分配和回收机制
- pigz多线程压缩文件
- 为什么使用EasyUI DataGrid进行分页设置pageSize却无效
- Bitmap的高效加载,避免内存溢出
- makefile双冒号规则
- android 创建文件时提示open failed: ENOENT (No such file or directory)
- HDFS学习笔记(4)IO读写操作之checksum
- Java第三章上机实践-实验1-计算电费