Pattern Reference
Suffix Automaton
"Minimal DFA accepting all suffixes of a string. Count distinct substrings, longest common substring, lexicographically smallest."
Loading...
Deep Dive Tutorial
A SAM state represents an equivalence class of substrings that all end at the same set of positions in the original string. The link tree (suffix links) forms a tree where each state's link points to the longest proper suffix of any string in that state's class. Build online: for each new character, create a new state, extend links, and handle possible cloning when a state needs to be split.
Suffix Automaton construction
class SAM {
constructor() {
this.states = [{len: 0, link: -1, next: {}}]; // state 0 = initial
this.last = 0;
this.size = 1;
}
extend(c) {
const cur = this.size++;
this.states.push({len: this.states[this.last].len + 1, link: -1, next: {}});
let p = this.last;
while (p !== -1 && !this.states[p].next[c]) {
this.states[p].next[c] = cur;
p = this.states[p].link;
}
if (p === -1) {
this.states[cur].link = 0;
} else {
const q = this.states[p].next[c];
if (this.states[p].len + 1 === this.states[q].len) {
this.states[cur].link = q;
} else {
// Clone q
const clone = this.size++;
this.states.push({
len: this.states[p].len + 1,
link: this.states[q].link,
next: {...this.states[q].next}
});
while (p !== -1 && this.states[p].next[c] === q) {
this.states[p].next[c] = clone;
p = this.states[p].link;
}
this.states[q].link = this.states[cur].link = clone;
}
}
this.last = cur;
}
build(s) { for (const c of s) this.extend(c); }
// Count distinct substrings = sum of (len[v] - len[link[v]]) over all states
countDistinct() {
return this.states.slice(1).reduce((s, st) =>
s + st.len - this.states[st.link].len, 0);
}
}Worked Problems
bot
SAM key facts:
- At most 2n states, 3n transitions
- Each state = set of substrings with identical endpos sets
- Suffix link tree = crucial for counting and tree DP
- Build online in O(n) time and space
Common SAM applications:
- Count distinct substrings: Σ(len[v] - len[link[v]])
- LCS of two strings: run second string through SAM of first, track max match
- Count occurrences of each substring: propagate size up suffix link tree
- Lexicographically k-th substring: DFS on DAG structure
vs Suffix Array: SA+LCP is more cache-friendly and often easier. SAM is better when you need to process characters online or need the DAG structure for traversal.
- At most 2n states, 3n transitions
- Each state = set of substrings with identical endpos sets
- Suffix link tree = crucial for counting and tree DP
- Build online in O(n) time and space
Common SAM applications:
- Count distinct substrings: Σ(len[v] - len[link[v]])
- LCS of two strings: run second string through SAM of first, track max match
- Count occurrences of each substring: propagate size up suffix link tree
- Lexicographically k-th substring: DFS on DAG structure
vs Suffix Array: SA+LCP is more cache-friendly and often easier. SAM is better when you need to process characters online or need the DAG structure for traversal.