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

如何用Trie计算所有子串的LCP长度总和?求可行方案

Hey there! Let's figure out what's going wrong with your Trie implementation and get you a working solution—whether you need the sum of LCPs for all substring pairs or all suffix pairs (since it's easy to mix these up).

First, let's clarify the problem you're solving:

  • If you need the sum of LCP lengths for all suffix pairs: This is a common problem, and your initial Trie approach is on the right track but probably has a bug in how you count or accumulate contributions.
  • If you need the sum of LCP lengths for all substring pairs: This is a different problem (with a much larger result!), and your Trie was likely inserting suffixes instead of all substrings, leading to wrong results.

Case 1: Sum of LCPs for All Suffix Pairs

This scenario uses a suffix Trie, where each node represents a shared prefix between some number of suffixes. For a node with count (number of suffixes passing through it), it contributes count * (count - 1) // 2 to the total sum—since every pair of those suffixes shares this prefix, adding 1 to their LCP length.

Fixed Trie Implementation

class TrieNode:
    def __init__(self):
        self.children = {}
        self.count = 0  # Number of suffixes passing through this node

class SuffixTrie:
    def __init__(self):
        self.root = TrieNode()
    
    def insert_suffix(self, suffix):
        node = self.root
        for char in suffix:
            if char not in node.children:
                node.children[char] = TrieNode()
            node = node.children[char]
            node.count += 1  # Increment count for every node along the suffix path
    
    def calculate_lcp_sum(self):
        total = 0
        stack = [self.root]
        while stack:
            node = stack.pop()
            # Add contribution from this node
            total += node.count * (node.count - 1) // 2
            # Push children to stack for traversal
            for child in node.children.values():
                stack.append(child)
        return total

# Test with "zzz"
s = "zzz"
trie = SuffixTrie()
for i in range(len(s)):
    trie.insert_suffix(s[i:])
print(trie.calculate_lcp_sum())  # Output: 4 (correct for suffix pairs)

Why This Works

For "zzz", we insert 3 suffixes: "zzz", "zz", "z". Each node along their paths gets their count incremented:

  • Root's 'z' child: count=3 → contributes 3*2//2=3
  • That 'z' child's 'z' child: count=2 → contributes 2*1//2=1
  • The deepest 'z' child: count=1 → contributes 0
    Total sum: 3+1=4, which matches the expected suffix pair LCP sum.

Case 2: Sum of LCPs for All Substring Pairs

This is what your original question mentions ("all substring pairs"). The sum is much larger because there are O(n²) substrings, leading to O(n⁴) pairs if done naively. A Trie can work here, but we need to insert all substrings (not just suffixes), and each node's count represents how many substrings start with the prefix up to that node. The total sum is again the sum of count*(count-1)//2 for all nodes.

Trie Implementation for Substring Pairs

class TrieNode:
    def __init__(self):
        self.children = {}
        self.count = 0  # Number of substrings starting with this prefix

class SubstringTrie:
    def __init__(self):
        self.root = TrieNode()
    
    def insert_substring(self, substring):
        node = self.root
        for char in substring:
            if char not in node.children:
                node.children[char] = TrieNode()
            node = node.children[char]
            node.count += 1
    
    def calculate_lcp_sum(self):
        total = 0
        stack = [self.root]
        while stack:
            node = stack.pop()
            total += node.count * (node.count - 1) // 2
            for child in node.children.values():
                stack.append(child)
        return total

# Test with "zzz"
s = "zzz"
trie = SubstringTrie()
for i in range(len(s)):
    for j in range(i+1, len(s)+1):
        trie.insert_substring(s[i:j])
print(trie.calculate_lcp_sum())  # Output: 18 (correct for substring pairs)

Limitation of This Approach

The problem with this Trie is space and time complexity: O(n²) in the worst case (e.g., a string with all unique characters). For n > 1000, this will be too slow or use too much memory.

More Efficient Solution: Suffix Automaton

For large n (up to 1e5 or more), a suffix automaton (SAM) is the way to go. SAM has O(n) space and time complexity, and we can compute the sum by leveraging its structure:

  • Each state in SAM represents a set of substrings with the same endpos.
  • For each state, len is the length of the longest substring in the set, link is the suffix link to the state representing shorter substrings, and size is the number of starting positions of substrings in this state.
  • The contribution of a state is (len[state] - len[link[state]]) * (size[state] * (size[state]-1) // 2). Summing this over all states gives the total sum of LCP lengths for all substring pairs.
class State:
    def __init__(self):
        self.len = 0
        self.link = -1
        self.next = {}
        self.size = 0  # Number of starting positions of substrings in this state

class SuffixAutomaton:
    def __init__(self):
        self.size = 1
        self.last = 0
        self.states = [State()]
    
    def sa_extend(self, c):
        p = self.last
        curr = self.size
        self.size += 1
        self.states.append(State())
        self.states[curr].len = self.states[p].len + 1
        self.states[curr].size = 1  # This state corresponds to a new ending position
        while p != -1 and c not in self.states[p].next:
            self.states[p].next[c] = curr
            p = self.states[p].link
        if p == -1:
            self.states[curr].link = 0
        else:
            q = self.states[p].next[c]
            if self.states[p].len + 1 == self.states[q].len:
                self.states[curr].link = q
            else:
                clone = self.size
                self.size += 1
                self.states.append(State())
                self.states[clone].len = self.states[p].len + 1
                self.states[clone].next = self.states[q].next.copy()
                self.states[clone].link = self.states[q].link
                self.states[clone].size = 0  # Clone doesn't represent a new starting position
                while p != -1 and self.states[p].next.get(c, -1) == q:
                    self.states[p].next[c] = clone
                    p = self.states[p].link
                self.states[q].link = clone
                self.states[curr].link = clone
        self.last = curr
    
    def calculate_lcp_sum(self):
        # First, sort states by length in descending order to propagate size
        order = sorted(range(self.size), key=lambda x: -self.states[x].len)
        for u in order:
            if self.states[u].link != -1:
                self.states[self.states[u].link].size += self.states[u].size
        
        total = 0
        for u in range(1, self.size):  # Skip the initial state
            link = self.states[u].link
            # Number of distinct substring lengths contributed by this state: len[u] - len[link]
            # Number of pairs of substrings starting with these prefixes: size[u] choose 2
            total += (self.states[u].len - self.states[link].len) * (self.states[u].size * (self.states[u].size - 1) // 2)
        return total

# Test with "zzz"
s = "zzz"
sam = SuffixAutomaton()
for c in s:
    sam.sa_extend(c)
print(sam.calculate_lcp_sum())  # Output: 18 (correct for substring pairs)

What Was Wrong With Your Original Trie?

Most likely, you were either:

  1. Inserting suffixes instead of all substrings when you needed substring pairs, leading to an undercount.
  2. Not incrementing the count for every node along the path (only marking the end of the suffix/substring), which would miss contributions from intermediate prefixes.
  3. Accumulating the sum incorrectly (e.g., not summing count*(count-1)//2 for every node).

内容的提问来源于stack exchange,提问作者Saurabh P Bhandari

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 09:23:10