The smallest deterministic automaton recognising all substrings of a string. Built online in , in about 30 lines — the best power-to-effort ratio of any string structure.

The construction

struct SAM {
    struct State { int len = 0, link = -1; map<char,int> nxt; };
    vector<State> st;
    int last;
 
    SAM() { st.push_back({}); last = 0; }
 
    void extend(char c) {
        int cur = st.size(); st.push_back({});
        st[cur].len = st[last].len + 1;
        int p = last;
        while (p != -1 && !st[p].nxt.count(c)) { st[p].nxt[c] = cur; p = st[p].link; }
        if (p == -1) st[cur].link = 0;
        else {
            int q = st[p].nxt[c];
            if (st[p].len + 1 == st[q].len) st[cur].link = q;
            else {
                int clone = st.size(); st.push_back(st[q]);      // copy transitions and link
                st[clone].len = st[p].len + 1;
                while (p != -1 && st[p].nxt[c] == q) { st[p].nxt[c] = clone; p = st[p].link; }
                st[q].link = clone;
                st[cur].link = clone;
            }
        }
        last = cur;
    }
};

Size: at most states and transitions. Both bounds are tight.

What the pieces mean

  • A state represents an equivalence class of substrings that occur at exactly the same set of end positions (its endpos set).
  • len[v] is the longest string in that class. The shortest is len[link[v]] + 1, so the state represents exactly distinct substrings.
  • link[v] (the suffix link) points to the state of the longest proper suffix in a different class. The links form a tree — the link tree.

The link tree of the suffix automaton of is the suffix tree of .

That identity is why the automaton answers suffix-tree questions.

What it answers

QuestionMethodCost
Is a substring?run through the automaton
Number of distinct substrings
Total length of all distinct substringssimilar sum
Number of occurrences of cnt of the state, propagated up the link tree
First occurrence position of firstpos of the state
All occurrence positionstraverse the link subtree
Longest common substring of two stringsrun through the SAM of , tracking the current match length
LCS of stringsrun each through, take the per-state minimum
-th lexicographic substringdescend using path counts
Smallest cyclic rotationbuild the SAM of , greedily take the smallest edge times

Occurrence counts

// cnt[v] = 1 for every non-clone state created by extend(), 0 for clones
// then propagate up the link tree in decreasing order of len
vector<int> order = sortStatesByLen();
for (int i = order.size() - 1; i > 0; i--) cnt[st[order[i]].link] += cnt[order[i]];

Sorting by len is a counting sort — the link tree is naturally ordered by length.

Longest common substring

int v = 0, l = 0, best = 0;
for (char c : t) {
    while (v && !st[v].nxt.count(c)) { v = st[v].link; l = st[v].len; }  // fall back
    if (st[v].nxt.count(c)) { v = st[v].nxt[c]; l++; }
    best = max(best, l);
}

Six lines, , and it is the standard solution to a problem that looks like it needs a suffix tree.

Transitions: map vs array

map<char,int>array<int,26>
Memory total
Access
fine208 MB — too much

Use array for small and a map (or a sorted vector) for large .

Generalised suffix automaton

To handle several strings, feed them all in, resetting last = 0 between them, and check for existing transitions before creating a state. Slightly fiddly; the alternative is to concatenate with distinct separators.

Suffix automaton vs suffix array

WantUse
Count/enumerate all substringssuffix automaton
Longest common substringsuffix automaton
Lexicographic order of suffixessuffix array
-th smallest suffixsuffix array
Minimum memorysuffix array
Online constructionsuffix automaton

See also: Suffix Array · Suffix Tree · Weiner