LCP的引入笔记

——《高级数据结构》
前面介绍了几种算法构造后缀数组,虽然得到的后缀数组已经能处理一些简单的问题,但是为了让其能够具有与后缀树相媲美的字符串处理能力,需要引入辅助工具——LCP(Longest Common Prefix,最长公共前缀)。
对于字符串St1和St2,它们的最长公共前缀LCP_Str(St1,St2)定义为最大的整数len,满足St1的len次方=St2的len次方(记号意义同前文中)。当然,len不会超过两者中较短字符串的长度。对于后缀数组,我们需要知道的是任意两个后缀的最长公共前缀 。因此定义LCP_Idx(i,j)=LCP_Str(Suffix(SA(i)),Suffix(SA(j))),即排名第i的后缀和排名为j的后缀的LCP。
不难发现,LCP_Idx与操作元顺序无关,并且对于两个相同的字符串,它们的LCP即它们的长度,因此为了求解方便,我们只需要求所有i<j的LCP_Idx(i,j).
如果用朴素的方法计算LCP,那么将会非常低效。由于是针对后缀计算LCP,借鉴倍增算法的思想,在这里需要利用题目的特殊性。以下给出一种线性时间复杂度的算法。
我们首先给出需要利用到的结论,其证明将在之后给出。
(1)对于i<j,LCP_idx(i,j)=min(LCP_Idx(k-1,k)|i+1<=k<=j)
(2)对于数组height,其中height[i]=LCP_Idx(i-1,i),对于i=0的边界情况,令height[0]=0。有了height数组,对于任意两个后缀SA(i)和SA(j),根据结论(1),只需要计算min{LCP_Idx(k-1,k)|i+1<=k<=j}。由于对于固定的后缀数组,其height数组也是固定的,因此该问题即经典的RMQ问题。通过对height数组进行RMQ的预处理,便可以在O(1)的时间内回答每一对后缀的LCP了。
(3)为了计算height数组,我们同样需要如下的结论;对于i>0且Rank(i)>0,有height[Rank(i)]>=height[Rank(i-1)]-1。据此按照i递增的顺序求解height[Rank(i)]。计算height[Rank(i)]时,不要从第一个字符开始比较,而是从第height[Rank(i-1)]个字符开始比较。
用步骤3的方法可以在O(n)的时间内得到height数组,至此可以完美解决本问题。
根据以上描述,不难得到如下的代码(仅构造height数组,RMQ过程省略):
//参数分别为:字符串st(下标从0开始),后缀数组指针,名次数组指针,待计算的height数组指针及字符串长度
void GetHeght(int* st,int* SA,int* rank,in* height,int n)
{
int L=0;
for (int i=0;i<n;++i)
if (rank[i]>0)
{
int j=SA[rank[i]-1];
while ((i+L<n)&&(j+L<n)&&(st[i+L]==st[j+L]))
L++;
height[rank[i]]=L;
if (L>0) L–;
}
}

下面给出以上相关的证明
1.对于i<j,LCP_Idx(i,j)=min(LCP_Idx(k-1,k)|i+1<=k<=j)
证明:步骤一:对任意0<=i<j<k<n;有LCP_Idx(i,k)=min(LCP_Idx(i,j),LCP_Idx(j,k))。假设len=min{LCP_Idx(i,j),LCP_Idx(j,k)},那么有Suffix(SA(i))的len次方=Suffix(SA(j))的len次方=Suffix(SA(k))的len次方,因此Suffix(SA(i))的len次方=Suffix(SA(j))的len次方,即LCP_Idx(i,k)>=len;同时,若有LCP_Idx(i,k)>len;假设为len2,那么Suffix(SA(i))的len2次方=Suffix(SA(k))的len2次方。由i<j<k可知,Suffix(SA(i))的len2次方<=Suffix(SA(j)的len2次方)<=Suffix(SA(k))的len2次方,可得min{LCP_Idx(i,j),LCP_Idx(j,k)}>=len2,矛盾,所以LCP_Idx(i,k)<=len。得证。
步骤二:应用步骤一的结论将LCP_Idx(i,j)展开。若i+1=j,那么显然成立;否则,答案为min(LCP_Idx(i,i+1)),LCP_Idx(i,i+1),LCP_Idx(i+1,j),第一项可以直接写出,第二项继续展开,最后的结果便是证明的式子。
2.对于i>0且Rank(i)>0,有height[Rank(i)]>=height[Rank(i-1)]-1
证明:当height[Rank(i-1)]<=1时显然成立,当height[Rank(i-1)]>1时,因为height[0]=0,所以Rank(i-1)>0。不妨令j=i-1,k=SA[Rank(j)-1],那么有height[Rank(i-1)]=LCP_str(Suffix(k),Suffix(j))>1。将这两个后缀各自去掉其第一个字符得到新的两个后缀为Suffix(k+1)和Suffix(j+1=i),显然LCP_Str(Suffix(k+1),Suffix(i))=LCP_Str(Suffix(k),Suffix(j))-1=height[Rank[i-1]]-1。同时,我们注意到,Suffix(k)<Suffix(j),去掉 各自首字母得到的后缀仍然满足“小于”关系,即Suffix(k+1)<Suffix(j+1=i),即Rank(k+1)<Rank(i)。根据证明1中的结论,由于Rank(i)-1>=Rank(k+1),故LCP_Idx(Rank(i)-1,Rank(i))>=LCP_Idx(Rank(k+1),Rank(i)).该不等式左边即为height[Rank(i)],而右边为height[Rank(i-1)]=-1,故得证。
3。计算height数组过程的时间复杂度为 O(n)
证明:根据2中的结论,我们可以按照i从小到大的顺序计算height数组。计算height[Rank(i)]时,对于i>.0且Rank(i)>0的情况,我们从height[Rank(i-1)]-1开始,逐一比较,该过程总时间复杂度为O(n),好比蜗牛爬杆,每天至多爬到顶,每天下降一步,最后上爬的步数至多为2n;而对于i=0或者Rank(i)=0的情况,即使从头开始顺次比较,最坏也只要各自比较n次。综上所述,总共比较的次数不会超过4n次,故算法的时间复杂度为O(n)。

全部评论

相关推荐

2025-12-12 19:01
南京航空航天大学 C++
秋招没咋投,准备&nbsp;wxg&nbsp;转正之后摆烂了。结果不堪字节&nbsp;HR&nbsp;的骚扰还是面了一下字节。之前想去字节的时候怎么面都挂。现在想着随便面一下结果三面技术面都意外顺利还有加面。十月中旬字节发了意向,wxg&nbsp;转正结果无响应。十月底字节拉了保温群,wxg&nbsp;口头通过,系统显示考核中。十一月初和字节&nbsp;ld&nbsp;交流之后得知&nbsp;base&nbsp;居然能选海外,甚至能小&nbsp;wlb&nbsp;一下,wxg&nbsp;无响应无人联系。十一月中旬把字节&nbsp;base&nbsp;转到了海外,wxg&nbsp;流程灰了,一问超时忘处理了,过两天又变考核中了。十一月下旬字节换了海外&nbsp;HR&nbsp;对接,问了期望薪资,wxg&nbsp;考核终于显示通过,无&nbsp;HR&nbsp;保温,无其他保温。十一月底给字节报了个天价,想吓吓他们,同时告诉微信字节要开了,微信无响应。同样十一月底字节&nbsp;HR&nbsp;告诉我确实给不到那么高,但是能拿期权补上,问能不能接受。微信无响应。同样十一月底字节&nbsp;HR&nbsp;告知了具体方案,符合预期。&nbsp;微信无响应。十二月上旬催&nbsp;wxg&nbsp;不开我就盲拒了,wxg&nbsp;HR&nbsp;火急火燎的打电话问情况,问期望。我给了一个不算夸张的总包数字,因为今年市场在涨,过了三天还不联系我,我再催,约时间下午打电话,非得在我给出的数字上压下去几万,微信又不差这点,为什么不能满足我,让我没有拒绝的理由呢?一番纠结抗争,求稳还是追求挑战,最终选择接受迎接新的挑战,因为堂吉诃德永远不会停下脚步!回想起来,在&nbsp;wxg&nbsp;谈薪的阶段,我认为并没有给予我一定的重视,即使&nbsp;HR&nbsp;表示我在实习期间的表现和之前的面评都很靠前。也没有感觉到想要争取我,虽然我表示拒了&nbsp;offer&nbsp;之后要给我加面委定&nbsp;t6&nbsp;再涨,但我三个月没面试让我面面委那就是白给,还是算了。有缘再见了我亲爱的&nbsp;wxg,再见了曾经的梦中情厂,再见亲爱的&nbsp;mt,再见亲爱的朋友们。也再见,北京的一切。我想润了。秋招结束,卸载牛客,下一个三年,下一个五年,下一个十年后再来看看。
面试中的大熊猫爱吃薯...:我嫉妒得狗眼通红
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务