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

寻找低于O(n²)时间复杂度的字符串超串筛选算法

高效筛选搜索查询中的最长超串(替代O(n²)暴力法)

针对你几十亿级搜索查询的超串筛选需求——保留那些无法被其他查询按单词顺序全包含的最长查询,暴力O(n²)的解法显然完全不可行,这里提供几个复杂度更低的高效思路,从算法设计到分布式处理都覆盖到:

核心思路:从长到短处理,避免重复检查

首先,我们可以把所有查询按单词数量从多到少排序。这样我们先处理最长的字符串:如果一个查询没有被任何已保留的更长查询包含,就将其保留;而短查询如果能被已保留的长查询包含,直接跳过即可。这个思路能避免暴力法中重复的两两比对。

方法一:前缀树(Trie)优化子序列匹配

前缀树非常适合处理这种单词序列的包含验证,因为我们需要检查的是「短查询是否是长查询的子序列(按顺序包含所有单词)」。具体实现步骤:

  • 预处理:将每个查询拆分为单词数组,按单词数降序排序。
  • 前缀树构建:遍历排序后的查询,对每个查询:
    1. 检查它是否能被已加入前缀树的某个查询序列覆盖(即是否是某个已保留查询的子序列)。如果是,直接跳过。
    2. 如果不能被覆盖,将该查询的单词序列插入前缀树,并加入最终保留集合。
  • 子序列快速验证:前缀树的每个节点存储下一个可能的单词,验证时只需按顺序遍历查询的单词,在前缀树中查找是否存在一条允许跳过中间单词的匹配路径。为了加速,可以给每个节点维护一个「后续单词位置映射」,记录在当前路径下每个后续单词第一次出现的位置,实现跳跃式查找。

方法二:倒排索引+候选过滤

如果前缀树实现起来有门槛,也可以用倒排索引来减少候选比对数量:

  • 构建倒排索引:为每个单词建立索引,记录包含该单词的所有查询,以及该单词在查询中的位置。
  • 按长度降序处理查询:对于每个查询,先通过倒排索引找到包含它所有单词的候选查询(仅限长度更长的),然后逐一验证这些候选查询是否按顺序包含当前查询的所有单词。
  • 优化验证:验证时可以用双指针法,快速判断短查询是否是长查询的子序列,时间复杂度为O(k)(k为长查询的单词数)。

分布式处理适配海量数据

面对几十亿条数据,单台机器内存和算力都不够,需要分片处理:

  • 按首单词分片:将首单词相同的查询分配到同一个处理节点,这样每个节点处理的数据量大幅降低。
  • 节点内独立处理:每个节点内部按上述任意一种方法处理,最后合并所有节点的保留集合即可。

伪代码示例(前缀树方案)

class TrieNode:
    def __init__(self):
        self.children = {}
        # 存储后续单词的位置映射,加速子序列查找
        self.next_word_pos = {}

class Trie:
    def __init__(self):
        self.root = TrieNode()
    
    def insert(self, word_list):
        node = self.root
        for idx, word in enumerate(word_list):
            if word not in node.children:
                node.children[word] = TrieNode()
            # 更新当前节点的后续单词位置映射
            for w in word_list[idx+1:]:
                if w not in node.next_word_pos:
                    node.next_word_pos[w] = idx+1
            node = node.children[word]
    
    def is_subsequence(self, word_list):
        node = self.root
        current_idx = 0
        for word in word_list:
            # 在前缀树中查找是否有后续路径包含当前单词
            while current_idx < len(node.next_word_pos):
                if word in node.next_word_pos:
                    current_idx = node.next_word_pos[word]
                    node = node.children[word]
                    break
                current_idx += 1
            else:
                return False
        return True

# 预处理步骤
raw_queries = ["cargo", "cargo pants", "cargo pants men buy", "cargo pants men", "cargo pants men melbourne buy"]
query_word_lists = [q.split() for q in raw_queries]
# 按单词数量降序排序
sorted_queries = sorted(query_word_lists, key=lambda x: len(x), reverse=True)

# 筛选最长超串
keep_set = set()
trie = Trie()

for q in sorted_queries:
    if not trie.is_subsequence(q):
        keep_set.add(" ".join(q))
        trie.insert(q)

print("保留的最长超串:", keep_set)

复杂度分析

  • 排序阶段:O(n log n),n为查询总数。
  • 每个查询的插入和验证:O(k),k为查询的单词数。
  • 总时间复杂度:O(nk log n),远低于暴力法的O(n²),完全适配几十亿级别的数据量。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 11:02:28