区间多字符串两两最长公共前缀查询的高效数据结构求解问询
首先得明确你问题里的“区间内所有字符串两两之间的最长公共前缀”具体指哪种场景——两种常见理解对应完全不同的解法,我都会帮你梳理清楚:
场景1:求区间内所有字符串共有的最长公共前缀
(即所有字符串都包含的最长前缀,比如区间内是["apple", "app", "application"],结果是"app")
这种场景用线段树就能高效解决,完全符合你的需求:
预处理步骤
- 前缀哈希预处理:
- 给每个字符串计算前缀哈希数组(推荐用双哈希降低冲突概率),这样可以
O(1)比较任意两个字符串的前k个字符是否相同。 - 例如,对于字符串
s,哈希数组h[i]表示s[0..i-1]的哈希值,pow_base[i]表示基数的i次幂,这样两个字符串s1和s2的前k个字符相等等价于h1[k] * pow_base[len(s2)] == h2[k] * pow_base[len(s1)](具体公式根据你选的哈希方式调整)。
- 给每个字符串计算前缀哈希数组(推荐用双哈希降低冲突概率),这样可以
- 构建线段树:
- 线段树的每个节点对应原数组的一个区间
[L, R],每个节点存储两个信息:lcp_len:该区间内所有字符串共有的最长公共前缀长度。sample:该区间的一个代表字符串(比如取区间第一个字符串s[L]),用于和其他区间的代表字符串比较前缀。
- 线段树的合并逻辑:
对于左右子节点left和right:- 先取初始候选长度
candidate = min(left.lcp_len, right.lcp_len)。 - 用哈希比较
left.sample和right.sample的前candidate个字符是否相同:- 如果相同,合并后的
lcp_len就是candidate; - 如果不同,通过二分查找找到最大的
k < candidate,使得两个字符串的前k个字符相同,这个k就是合并后的lcp_len。
- 如果相同,合并后的
- 先取初始候选长度
- 线段树构建的时间复杂度是
O(n log n * log L),其中L是字符串的最大长度,结合总字符串长度≤1e5的条件,完全可行。
- 线段树的每个节点对应原数组的一个区间
查询操作
对于任意查询[l, r],将区间拆分为线段树中的若干节点,依次合并这些节点的lcp_len和sample信息,最终得到的lcp_len就是答案。单次查询时间复杂度是O(log n * log L)。
场景2:求区间内任意两两字符串的LCP的最大值
(即找到区间内一对字符串,它们的LCP是所有两两对中最大的,比如区间内是["apple", "app", "banana"],结果是3,来自"apple"和"app"的LCP)
这种场景需要结合后缀数组、RMQ和离线处理,复杂度可以控制在O((n+q) log n):
预处理步骤
- 构建后缀数组与相关结构:
- 将所有字符串拼接成一个大字符串,每个字符串之间用一个不在原字符集中的分隔符(比如
#)隔开,确保不同字符串的后缀不会产生虚假的公共前缀。 - 对这个大字符串构建后缀数组
SA、排名数组rank(rank[i]表示原数组第i个字符串对应的后缀在SA中的排名),以及height数组(height[i]表示SA[i]和SA[i-1]对应的后缀的LCP长度)。 - 对
height数组构建ST表,支持O(1)查询任意区间的最小值(这个最小值就是对应两个后缀的LCP长度)。
- 将所有字符串拼接成一个大字符串,每个字符串之间用一个不在原字符集中的分隔符(比如
- 离线预处理查询与边权:
- 我们知道,两两字符串的最大LCP一定出现在字典序相邻的字符串对中(证明:对于三个字典序排序后的字符串
s1 < s2 < s3,LCP(s1,s3) = min(LCP(s1,s2), LCP(s2,s3)),不可能大于其中任意一个)。 - 因此,我们只需要关注字典序相邻的字符串对,计算它们的LCP值(通过ST表查询
height区间的最小值),并将这些对转化为(a, b, val),其中a和b是这对字符串在原数组中的位置(确保a < b),val是它们的LCP值。
- 我们知道,两两字符串的最大LCP一定出现在字典序相邻的字符串对中(证明:对于三个字典序排序后的字符串
离线查询处理(线段树+排序)
- 排序准备:
- 将所有查询按右端点
r从小到大排序,每个查询记录l, r, 查询索引(用于最后输出答案)。 - 将所有
(a, b, val)按b从小到大排序。
- 将所有查询按右端点
- 线段树维护最大值:
- 初始化一个支持单点更新和区间最大值查询的线段树,初始值全为0。
- 用两个指针分别遍历查询和点对:
- 对于当前处理的查询(右端点为
r),先将所有b ≤ r的点对加入线段树:更新线段树中位置a的值为max(当前值, val)(因为这对字符串的两个位置都≤r,只要a ≥ l就属于查询区间)。 - 查询线段树的区间
[l, r]的最大值,这个值就是该查询的答案(如果区间长度为1,答案为0)。
- 对于当前处理的查询(右端点为
内容的提问来源于stack exchange,提问作者Dean Ambrose
相关产品推荐
相关产品推荐

