You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

含排列的字符串向量中两字符串LCP计算的性能问题求助

我之前也碰到过类似的性能瓶颈——双重循环遍历区间内所有字符串对的思路虽然直观,但一旦区间规模或者查询数量上去,O(QK²L)的时间复杂度(Q是查询数,K是区间大小,L是字符串平均长度)会直接让程序跑不动。下面给你几个从易实现到高性能的优化方案,按需选择:

方法一:排序+滑动窗口(中小规模数据首选)

这个方法实现最简单,适合数据量不大的场景,核心思路是:区间内的最长LCP一定出现在字典序相邻的两个字符串之间。因为如果有三个字符串A<B<C,那么LCP(A,C) ≤ min(LCP(A,B), LCP(B,C)),所以只要找排序后相邻对的最大LCP就行。

具体步骤

  1. 全局预处理排序:把所有字符串和它们的原始索引绑定,按字典序排序。
  2. 处理每个查询:
    • 从全局排序后的列表中筛选出原始索引在查询区间内的字符串,形成候选列表。
    • 遍历候选列表的相邻元素,计算它们的LCP,取最大值作为答案。
  3. 快速计算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)。

核心思路

  1. 构建字典树:每个节点记录经过它的字符串的最小和最大原始索引。这样,某个节点对应的前缀长度是它的深度,若该节点的min_idx ≤ r且max_idx ≥ l,说明区间[l, r]内至少有两个字符串共享这个前缀。
  2. 离线处理查询:把所有查询按答案的可能最大值从大到小处理——先遍历字典树中深度最大的节点,找到所有未解决且符合条件的查询,将它们的答案设为当前节点的深度(因为深度越大,前缀越长,第一个匹配的就是最优解)。

方法三:后缀数组+RMQ+主席树(超大规模数据终极方案)

如果要处理百万级别的字符串和查询,这个方案是最优的,时间复杂度为O(NL log NL + Q log N)。核心思路是:

  1. 把所有字符串用唯一分隔符拼接成大字符串,构建后缀数组和LCP数组。
  2. 用RMQ结构快速查询LCP数组的区间最小值,用主席树维护原始索引对应的后缀排名范围。
  3. 每个查询转化为在主席树中找到区间内后缀的排名范围,再查询该范围内LCP数组的最大值,即为答案。

这个方案实现复杂度较高,但性能极强,适合工业级的大数据场景。


内容的提问来源于stack exchange,提问作者Kila30

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 04:21:49