含排列的字符串向量中两字符串LCP计算的性能问题求助
我之前也碰到过类似的性能瓶颈——双重循环遍历区间内所有字符串对的思路虽然直观,但一旦区间规模或者查询数量上去,O(QK²L)的时间复杂度(Q是查询数,K是区间大小,L是字符串平均长度)会直接让程序跑不动。下面给你几个从易实现到高性能的优化方案,按需选择:
方法一:排序+滑动窗口(中小规模数据首选)
这个方法实现最简单,适合数据量不大的场景,核心思路是:区间内的最长LCP一定出现在字典序相邻的两个字符串之间。因为如果有三个字符串A<B<C,那么LCP(A,C) ≤ min(LCP(A,B), LCP(B,C)),所以只要找排序后相邻对的最大LCP就行。
具体步骤
- 全局预处理排序:把所有字符串和它们的原始索引绑定,按字典序排序。
- 处理每个查询:
- 从全局排序后的列表中筛选出原始索引在查询区间内的字符串,形成候选列表。
- 遍历候选列表的相邻元素,计算它们的LCP,取最大值作为答案。
- 快速计算LCP:用前缀哈希+二分查找替代逐字符比较,把单对字符串的LCP计算从O(L)降到O(logL)。
代码示例(C++)
#include <vector> #include <string> #include <algorithm> #include <iostream> using namespace std; // 前缀哈希预处理 const int BASE = 911382629; const int MOD = 1e9 + 7; vector<vector<long long>> hash_prefixes; vector<long long> powers; void precompute_hashes(const vector<string>& strings) { // 预处理幂次 int max_len = 0; for (const auto& s : strings) max_len = max(max_len, (int)s.size()); powers.resize(max_len + 1); powers[0] = 1; for (int i = 1; i <= max_len; ++i) { powers[i] = (powers[i-1] * BASE) % MOD; } // 预处理每个字符串的前缀哈希 for (const auto& s : strings) { vector<long long> hash(s.size() + 1, 0); for (int i = 0; i < s.size(); ++i) { hash[i+1] = (hash[i] * BASE + s[i]) % MOD; } hash_prefixes.push_back(hash); } } // 计算两个字符串的LCP长度(用哈希+二分) int compute_lcp(const string& a, const string& b, int a_idx, int b_idx) { int min_len = min(a.size(), b.size()); int low = 0, high = min_len; int ans = 0; while (low <= high) { int mid = (low + high) / 2; // 计算a前mid个字符的哈希 long long hash_a = hash_prefixes[a_idx][mid]; // 计算b前mid个字符的哈希 long long hash_b = hash_prefixes[b_idx][mid]; if (hash_a == hash_b) { ans = mid; low = mid + 1; } else { high = mid - 1; } } return ans; } int main() { // 示例输入处理 int n; cin >> n; vector<string> strings(n); for (int i = 0; i < n; ++i) { cin >> strings[i]; } precompute_hashes(strings); // 全局排序(绑定原始索引,1-based) vector<pair<string, int>> sorted_strs; for (int i = 0; i < n; ++i) { sorted_strs.emplace_back(strings[i], i+1); } sort(sorted_strs.begin(), sorted_strs.end()); int q; cin >> q; while (q--) { int l, r; cin >> l >> r; // 筛选候选字符串 vector<pair<string, int>> candidates; for (const auto& p : sorted_strs) { if (p.second >= l && p.second <= r) { candidates.push_back(p); } } // 计算最大LCP int max_lcp = 0; for (int i = 1; i < candidates.size(); ++i) { // 找到原始索引对应的哈希前缀下标 int idx1 = candidates[i-1].second - 1; int idx2 = candidates[i].second - 1; int current_lcp = compute_lcp(candidates[i-1].first, candidates[i].first, idx1, idx2); max_lcp = max(max_lcp, current_lcp); } cout << max_lcp << " "; } return 0; }
方法二:字典树+离线查询(大规模数据高效方案)
如果你的数据量很大(比如N和Q都超过1e4),可以用字典树结合离线查询的思路,时间复杂度能降到O(N*L + Q log Q)。
核心思路
- 构建字典树:每个节点记录经过它的字符串的最小和最大原始索引。这样,某个节点对应的前缀长度是它的深度,若该节点的
min_idx ≤ r且max_idx ≥ l,说明区间[l, r]内至少有两个字符串共享这个前缀。 - 离线处理查询:把所有查询按答案的可能最大值从大到小处理——先遍历字典树中深度最大的节点,找到所有未解决且符合条件的查询,将它们的答案设为当前节点的深度(因为深度越大,前缀越长,第一个匹配的就是最优解)。
方法三:后缀数组+RMQ+主席树(超大规模数据终极方案)
如果要处理百万级别的字符串和查询,这个方案是最优的,时间复杂度为O(NL log NL + Q log N)。核心思路是:
- 把所有字符串用唯一分隔符拼接成大字符串,构建后缀数组和LCP数组。
- 用RMQ结构快速查询LCP数组的区间最小值,用主席树维护原始索引对应的后缀排名范围。
- 每个查询转化为在主席树中找到区间内后缀的排名范围,再查询该范围内LCP数组的最大值,即为答案。
这个方案实现复杂度较高,但性能极强,适合工业级的大数据场景。
内容的提问来源于stack exchange,提问作者Kila30
相关产品推荐
相关产品推荐

