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)。

全部评论

相关推荐

HoePointer:把重点可以标黑,简历精简一下,然后把你的项目放在 github 或者 gitee 上面,readme 写好看一点(一般面试官有可能会翻你的网页)
点赞 评论 收藏
分享
“无名小卒,还是名扬天下?”我知道很多人都不觉得我能走到今天这一步,当然,也包括我自己。在我的人生里,有两部作品刻下了最深的烙印:《斗破苍穹》与《龙族》。它们总被人拿来对照:一边是萧炎的桀骜轻狂,一边是路明非的怯懦衰颓。有人说,天蚕土豆没见过魂天帝,但江南见过真凯撒。我时常觉得,自己就是那个衰小孩路明非。可路明非可以开挂,我不可以;我也无数次幻想过,能拥有萧炎那般年少轻狂的人生,可我没有他与生俱来的逆天天赋。我只是个平庸的普通人,一个看过《斗破苍穹》却开不了挂的路明非,只能一步一步往上爬。从我下定决心找实习的那一刻起,我就给自己定下了目标:“我一定要为字节跳动卖命.jpg”。萧炎有他的三年之约,我有我的两年半之约(其实是一年半)。2024.11.20,科大讯飞的第一封实习offer落进邮箱,我迈出了这场奔赴的第一步。2025.8.18,放弃百度转正的安稳机会,转身走进前路未卜的不确定里。2025.11.14,我选择走进字节跳动,以实习生的身份重新出发。2026.3.25&nbsp;-&nbsp;3.31,一周速通上海飞书,幸遇赏识我的伯乐,斩获Special&nbsp;Offer。被告知面试通过的那一刻,我的内心无比平静,就像这个offer本就该属于我。不是侥幸,是应得的。这一路,有人看轻过我的出身,不相信我能走到这里;也有人在我看不见前路的时候,替我举过灯。没有他们的鼓励与支撑,就没有今天站在这里的我。我看到了自强不息的激荡,那是一个双非的伟大乐章!我是雨夜迈巴赫,我要开启属于我的新篇章了。
在看牛客的本杰明很勇...:真心祝贺l总 我永远的偶像 我滴神
春招至今,你收到几个面试...
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

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