Suffix Automaton (SAM)

Notation

  • Σ\Sigma: the alphabet. The alphabet size is ∣Σ∣=k|\Sigma| = k.
  • ss: a string. Its length is ∣s∣=n|s| = n, and its characters are indexed starting from 00.
  • t0t_0: the initial state.
  • endpos⁡(t)\operatorname{endpos}(t): the set of ending positions in string ss of substring tt.
  • link⁡(v)\operatorname{link}(v): the suffix link of state vv.
  • len⁡(v)\operatorname{len}(v): the length of the longest substring corresponding to state vv.
  • longest⁡(v)\operatorname{longest}(v): the longest substring corresponding to state vv.
  • minlen⁡(v)\operatorname{minlen}(v): the length of the shortest substring corresponding to state vv.
  • shortest⁡(v)\operatorname{shortest}(v): the shortest substring corresponding to state vv.

Overview of suffix automata

A suffix automaton (SAM) is a powerful data structure that can solve many string-related problems.

For example, the following string problems can all be solved with a SAM in linear time:

  • Finding all occurrences of a string in another string;
  • Counting the number of distinct substrings in a given string.

Intuitively, the SAM of a string can be understood as a compressed representation of all substrings of the given string. It is worth noting that a SAM stores all this information in a highly compressed form. For a string of length nn, its space complexity is only O(n)O(n). Moreover, the time complexity of constructing a SAM is also only O(n)O(n). More precisely, a SAM has at most 2n−12n-1 nodes and 3n−43n-4 transition edges.

Definition

The SAM of a string ss is the minimal DFA that accepts all suffixes of ss.

In other words:

  • A SAM is a directed acyclic graph. Its nodes are called states, and its edges are called transitions between states.
  • The graph has a source t0t_0, called the initial state; every other node is reachable from t0t_0.
  • Every transition is labeled with a character. All transitions leaving a node have different labels.
  • There are one or more terminal states. If, starting from the initial state t0t_0, we eventually reach a terminal state, then the labels of all transitions along the path, concatenated, must form a suffix of ss. Conversely, every suffix of ss can be formed by a path from t0t_0 to some terminal state.
  • Among all automata satisfying the conditions above, the SAM has the fewest nodes.

The key to a SAM is precisely this minimality. In fact, directly building an AC automaton for all suffixes of ss also produces a DFA accepting all suffixes of ss. However, in the worst case, the resulting automaton has Θ(n2)\Theta(n^2) nodes, making its complexity unacceptable. As the example below shows, many nodes in the DFA obtained by building an AC automaton for all suffixes are duplicates and can therefore be merged. A SAM takes this merging of nodes to its extreme, keeping the resulting DFA's size at O(n)O(n). In this sense, a SAM is a "compressed" AC automaton for all suffixes of a string.

Substrings and paths

The simplest and most important property of a SAM is that it contains information about all substrings of ss. Any path starting from the initial state t0t_0, if we write down all transition labels along it, forms a substring of ss. Conversely, every substring of ss corresponds to some path starting from t0t_0.

For simplicity, we say that a substring corresponds to the path whose labels, starting from t0t_0, form that substring. Conversely, we say that any path corresponds to the string formed by its labels.

There may be more than one path reaching a state, so we say that a state corresponds to a set of strings, with the strings in this set corresponding to those paths respectively.

Simple examples

Here we show several suffix automata for simple strings.

We use blue for the initial state and green for terminal states.

For the string s=∅s=\varnothing:

Figure: SA

For the string s=as=\mathtt{a}:

Figure: SAa

For the string s=aas=\mathtt{aa}:

Figure: SAaa

For the string s=abs=\mathtt{ab}:

Figure: SAab

For the string s=abbs=\mathtt{abb}:

Figure: SAabb

For the string s=abbbs=\mathtt{abbb}:

Figure: SAabbb

In the last example, if we directly build the AC automaton for all its suffixes, the paths bbb\mathtt{bbb} and abbb\mathtt{abbb} should lead to different nodes. However, both nodes are terminal states, and appending any further character will never produce a longer matching string. This shows that the two nodes have the same behavior with respect to transitions in the automaton, so they can be merged into one node. This gives the SAM shown in the figure. The discussion below extends this idea of merging nodes to all cases and explains that, as long as nodes are merged appropriately, the resulting SAM has only O(n)O(n) nodes and transitions.

A linear-complexity construction algorithm

Before describing the algorithm for constructing a SAM in linear time, we need to introduce two concepts that are important for understanding the construction and give brief proofs of their properties. The ending-position set endpos⁡\operatorname{endpos} defines the nodes in a SAM (that is, it gives the necessary and sufficient condition for nodes to be merged), while the suffix link link⁡\operatorname{link} is simply the natural counterpart of the failure pointer in an AC automaton.

Ending positions endpos

For string ss, consider any nonempty substring denoted tt. We denote by endpos⁡(t)\operatorname{endpos}(t) the set of all ending positions of tt in ss (assuming that the characters in the string are numbered starting from zero). For example, for the string abcbc\mathtt{abcbc}, we have endpos⁡(bc)={2,4}\operatorname{endpos}(\mathtt{bc})=\{2,4\}.

Two substrings t1t_1 and t2t_2 may have exactly the same ending positions: endpos⁡(t1)=endpos⁡(t2)\operatorname{endpos}(t_1)=\operatorname{endpos}(t_2). This defines an equivalence relation among the substrings of ss. All nonempty substrings of ss can be divided into several equivalence classes according to their ending-position sets endpos⁡\operatorname{endpos}.

It is a fact that each such equivalence class corresponds to one state of the SAM1. In other words, whenever two substrings have the same ending positions, their paths in the SAM correspond to the same state. Equivalently, every non-initial state in a SAM corresponds to one or more nonempty substrings with the same endpos⁡\operatorname{endpos}. In short, the states of a SAM are the equivalence classes of all nonempty substrings, together with the initial state.

For now, accept this fact; we will use it to introduce the SAM construction algorithm. We will also show that the SAM satisfies all the required properties except minimality, which follows from the Myhill–Nerode theorem.

The values of endpos⁡\operatorname{endpos} give us several important conclusions that explain the relationship between different substrings corresponding to the same state.

Lemma 1

For string ss, let uu and ww be two nonempty substrings (assume ∣u∣≤∣w∣\left|u\right|\le \left|w\right|). They have the same endpos⁡\operatorname{endpos} if and only if every occurrence of uu in ss occurs as a suffix of ww.

Proof

The lemma is obvious. If uu and ww have the same endpos⁡\operatorname{endpos}, then uu is a suffix of ww, and in ss it occurs only as a suffix of ww. Conversely, by definition, if uu is a suffix of ww and occurs in ss only as a suffix of ww, then the two substrings have the same endpos⁡\operatorname{endpos}.

Lemma 2

Consider two nonempty substrings uu and ww (assume ∣u∣≤∣w∣\left|u\right|\le \left|w\right|). Then either endpos⁡(u)∩endpos⁡(w)=∅\operatorname{endpos}(u)\cap \operatorname{endpos}(w)=\varnothing or endpos⁡(w)⊆endpos⁡(u)\operatorname{endpos}(w)\subseteq \operatorname{endpos}(u), depending on whether uu is a suffix of ww:

{endpos⁡(w)⊆endpos⁡(u),if u is a suffix of w,endpos⁡(w)∩endpos⁡(u)=∅,otherwise.\begin{cases} \operatorname{endpos}(w) \subseteq \operatorname{endpos}(u), & \text{if } u \text{ is a suffix of } w, \\ \operatorname{endpos}(w) \cap \operatorname{endpos}(u) = \varnothing, & \text{otherwise}. \end{cases}
Proof

If the sets endpos⁡(u)\operatorname{endpos}(u) and endpos⁡(w)\operatorname{endpos}(w) have at least one common element, then since uu and ww end at the same position, uu is a suffix of ww. Therefore, wherever ww occurs, the substring uu also occurs. Hence endpos⁡(w)⊆endpos⁡(u)\operatorname{endpos}(w)\subseteq \operatorname{endpos}(u).

Lemma 3

Consider an equivalence class of substrings with the same endpos⁡\operatorname{endpos}, and sort all substrings in the class by nonincreasing length. Then each substring is no longer than the preceding one, and it is also a suffix of the preceding one. In other words, for any two substrings in the same equivalence class, the shorter one is a suffix of the longer one, and the lengths of the substrings in the class are consecutive, taking every integer value in some interval.

Proof

If the equivalence class contains only one substring, the lemma is obvious. We now consider an equivalence class containing more than 11 substring.

By Lemma 1, among any two distinct strings with the same endpos⁡\operatorname{endpos}, one must be longer and one shorter, and the shorter one is always a proper suffix of the longer one. Thus, no two strings in the equivalence class have the same length.

Let ww be the longest string in the equivalence class and uu the shortest. By Lemma 1, uu is a proper suffix of ww. Now consider any suffix of ww whose length lies in the interval [∣u∣,∣w∣][\left|u\right|,\left|w\right|]. It is easy to see that this suffix is also in the same equivalence class, because it can occur in ss only as a suffix of ww (this is because the shorter suffix uu occurs in ss only as a suffix of ww). Therefore, by Lemma 1, this suffix has the same endpos⁡\operatorname{endpos} as ww.

In one sentence, the substrings corresponding to the same state have distinct lengths, and those lengths are consecutive natural numbers; the shorter substrings are always suffixes of the longer ones.

Consider a state v≠t0v\neq t_0 in the SAM. We already know that state vv corresponds to an equivalence class of substrings with the same endpos⁡\operatorname{endpos}. Let ww be the longest of these strings; then all the other strings are suffixes of ww.

We also know that the first few suffixes of ww (considered in decreasing order of length) all belong to this equivalence class, while the other suffixes (at least one—the empty suffix) belong to other equivalence classes. Let tt be the longest among these other suffixes, and connect the suffix link of vv to tt.

In other words, for vv, the state reached by its suffix link link⁡(v)\operatorname{link}(v) corresponds to the longest suffix of ww whose endpos⁡\operatorname{endpos} set differs from it. It is also the longest suffix of ww that occurs more often in ss than ww.

For convenience, we define the equivalence class corresponding to the initial state t0t_0 to contain only the empty string, with endpos⁡(t0)={−1,0,…,∣S∣−1}\operatorname{endpos}(t_0)=\{-1,0,\ldots,\left|S\right|-1\}.

Lemma 4

All suffix links form a tree rooted at t0t_0.

Proof

Consider any state v≠t0v\neq t_0. The state reached by link⁡(v)\operatorname{link}(v) corresponds to a strictly shorter string (by the definition of a suffix link and Lemma 3). Therefore, by following suffix links, we always eventually reach the initial state t0t_0, which corresponds to the empty string.

Lemma 5

The tree constructed with endpos⁡\operatorname{endpos} sets as nodes and set inclusion as edges (that is, the endpos⁡\operatorname{endpos} set of each child is contained in the endpos⁡\operatorname{endpos} set of its parent) is the same as the tree constructed using suffix links link⁡\operatorname{link}.

Proof

By Lemma 2, the endpos⁡\operatorname{endpos} sets of any SAM form a tree (because any two sets are either disjoint or one is a subset of the other).

Now consider any state v≠t0v\neq t_0 and its suffix link link⁡(v)\operatorname{link}(v). From the suffix link and Lemma 2, we obtain

endpos⁡(v)⊊endpos⁡(link⁡(v)).\operatorname{endpos}(v)\subsetneq \operatorname{endpos}(\operatorname{link}(v)).

Note that this should be ⊊\subsetneq rather than ⊆\subseteq, because if endpos⁡(v)=endpos⁡(link⁡(v))\operatorname{endpos}(v)=\operatorname{endpos}(\operatorname{link}(v)), then vv and link⁡(v)\operatorname{link}(v) should be merged into one node.

Combining the preceding lemmas, we see that the tree formed by suffix links is essentially the tree formed by the endpos⁡\operatorname{endpos} sets.

Here is an example of the suffix-link tree produced when constructing a SAM for the string abcbc\mathtt{abcbc}. Each node is labeled with the longest substring in its corresponding equivalence class.

Figure: SA suffix links

The following observations about the figure are helpful for understanding the construction algorithm and applications below.

Explanation of the figure
  • The SAM contains a longest path whose label is exactly the string abcbc\mathtt{abcbc} itself. Starting from the initial state, each state along this path corresponds to a prefix of abcbc\mathtt{abcbc} (∅,a,ab,abc,abcb,abcbc\varnothing,\mathtt{a},\mathtt{ab},\mathtt{abc},\mathtt{abcb},\mathtt{abcbc}). These states are crucial in the later applications.

  • The suffix-link tree can be viewed as the result of "compressing" the paths that move these "prefix states" along suffix links to the root (the initial state).

    • Along each path, the sets of strings corresponding to the nodes form a partition of all suffixes of the corresponding prefix. For example, the path obtained by following suffix links from the state labeled abcbc\mathtt{abcbc} to the root is abcbc→bc→∅\mathtt{abcbc}\rightarrow\mathtt{bc}\rightarrow\varnothing. The node abcbc\mathtt{abcbc} actually corresponds to the set {abcbc,bcbc,cbc}\{\mathtt{abcbc},\mathtt{bcbc},\mathtt{cbc}\}, the node bc\mathtt{bc} actually corresponds to {bc,c}\{\mathtt{bc},\mathtt{c}\}, and the node ∅\varnothing corresponds to the empty string.
    • Different paths may share the same node, which is why there is "compression". For example, the paths abc→bc→∅\mathtt{abc}\rightarrow\mathtt{bc}\rightarrow\varnothing and abcbc→bc→∅\mathtt{abcbc}\rightarrow\mathtt{bc}\rightarrow\varnothing share the node bc\mathtt{bc}. This is because endpos⁡(bc)={2,4}\operatorname{endpos}(\mathtt{bc})=\{2,4\}, while at position 22 the string bc\mathtt{bc} is immediately preceded by a\mathtt{a}, and at position 44 the string bc\mathtt{bc} is immediately preceded by c\mathtt{c}. Thus, when a character is added to the front (that is, when moving against the suffix links), the set of ending positions (and hence the state) splits.
    • The suffix-link tree only needs to merge these suffix paths together appropriately; there is no need to consider other nodes. This is because every substring is a suffix of some prefix, so it must appear on one of these paths. The construction algorithm described later essentially adds characters one by one, constructs such a suffix path for each newly added prefix, and "compresses" it appropriately into the paths that already exist (that is, it does not reconstruct states or transitions that already exist).
    • The terminal states are exactly all nodes on the suffix path containing the string abcbc\mathtt{abcbc} itself.
  • Transitions reaching the same state must have the same label, and their source states must lie on some (continuous) path in the suffix-link tree. For example, there are two states with a transition to state abcb\mathtt{abcb}: abc\mathtt{abc} and bc\mathtt{bc}. They lie on the suffix-tree path abc→bc\mathtt{abc}\rightarrow\mathtt{bc}. Note that they correspond to the string sets {abc}\{\mathtt{abc}\} and {bc,c}\{\mathtt{bc},\mathtt{c}\}, respectively. Appending the character b\mathtt{b} to these strings gives state abcb\mathtt{abcb}, whose corresponding string set is {abcb,bcb,cb}\{\mathtt{abcb},\mathtt{bcb},\mathtt{cb}\}.

    • After adding a character, different states may transition to the same state because the new character makes it harder for ending positions to be added.
  • In the suffix-link tree, the endpos⁡\operatorname{endpos} set of each node is the union of the endpos⁡\operatorname{endpos} sets of its children, with at most one additional position. Moreover, this new position exists if and only if the node corresponds exactly to a prefix of the original string ending at that position. In the figure, none of the non-root, non-leaf nodes in the suffix-link tree corresponds to a prefix of abcbc\mathtt{abcbc}, so this situation does not occur.

The suffix automaton stores information about all substrings of a string. This can be understood from two perspectives:

  • The SAM itself can be viewed as a compressed version of the AC automaton for all suffixes of the string. Therefore, it stores information about all prefixes of all suffixes, which is equivalent to storing information about all substrings of the string.
  • The suffix-link tree of a SAM can be viewed as a compressed version of the suffix paths of all prefixes of the string. Therefore, it stores information about all suffixes of all prefixes, which is also equivalent to storing information about all substrings of the string.

Both perspectives are useful when handling different problems.

Summary

Before discussing the algorithm itself further, let us summarize the preceding material and introduce some auxiliary notation.

  • The substrings of ss can be divided into equivalence classes according to their ending-position sets endpos⁡\operatorname{endpos};

  • A SAM consists of the initial state t0t_0 and one state corresponding to each endpos⁡\operatorname{endpos} equivalence class of nonempty substrings;

  • Each state vv matches one or more substrings. Let longest⁡(v)\operatorname{longest}(v) be the longest of them and let len⁡(v)\operatorname{len}(v) be its length. Similarly, let shortest⁡(v)\operatorname{shortest}(v) be the shortest substring and let its length be minlen⁡(v)\operatorname{minlen}(v). Then all strings corresponding to this state are distinct suffixes of longest⁡(v)\operatorname{longest}(v), and their lengths take every integer value in the interval [minlen⁡(v),len⁡(v)][\operatorname{minlen}(v),\operatorname{len}(v)].

  • For any state v≠t0v\neq t_0, define its suffix link as an edge to the suffix of longest⁡(v)\operatorname{longest}(v) whose length is minlen⁡(v)−1\operatorname{minlen}(v)-1. The suffix links starting at the root t0t_0 form a tree. This tree also represents the inclusion relationships between the endpos⁡\operatorname{endpos} sets.

  • For any state v≠t0v\neq t_0, minlen⁡(v)\operatorname{minlen}(v) can be expressed using its suffix link link⁡(v)\operatorname{link}(v):

    minlen⁡(v)=len⁡(link⁡(v))+1.\operatorname{minlen}(v)=\operatorname{len}(\operatorname{link}(v))+1.
  • If we start at any state v0v_0 and traverse suffix links, we will always reach the initial state t0t_0. In this process we obtain a sequence of pairwise disjoint intervals [minlen⁡(vi),len⁡(vi)][\operatorname{minlen}(v_i),\operatorname{len}(v_i)], whose union forms the continuous interval [0,len⁡(v0)][0,\operatorname{len}(v_0)].

Algorithm

We can now discuss the SAM construction algorithm. This is an online algorithm: we can add the characters of the string one by one and maintain the SAM accordingly at each step.

Before discussing the detailed implementation, let us first use the figures to get an initial feeling for how the SAM may change when a new character cc is added.

An intuitive view of the incremental construction

Given the SAM of a string ss, we can construct the SAM of s+cs+c. According to the explanation of the figures above, we only need to construct the suffix path of the newly added prefix (namely s+cs+c) and compress it into the existing paths. Furthermore, as described above, every node on the new suffix path must be reachable by a transition labeled cc from a node on a suffix path of the original string ss.

Before adding the new character cc to the original string ss, we first consider what form its suffix path can have and how it can transition through cc. The most general case is shown below:

Figure: sam suffix path 1

In the figure, the suffix path of the original string ss is p0→p1→⋯→p6→t0p_0\rightarrow p_1\rightarrow\cdots\rightarrow p_6\rightarrow t_0, and suffix links are shown by red arrows. Some nodes (namely p2∼p6p_2\sim p_6) already have transitions labeled cc; because appending the same character to a sequence of consecutive suffixes again produces consecutive suffixes, the endpoints of these transitions form another suffix path q1→q2→q3→t0q_1\rightarrow q_2\rightarrow q_3\rightarrow t_0. There are two observations:

  • On the suffix path of the original string ss, the nodes without a transition labeled cc must be the first few nodes. Once some node (the node p2p_2 in the figure) has a transition labeled cc, every subsequent node must also have a transition labeled cc.

    Explanation: Let s2=longest⁡(p2)s_2=\operatorname{longest}(p_2). Every subsequent node corresponds to a suffix of s2s_2. Thus, if s2+cs_2+c also occurs in ss, then every suffix of s2s_2 followed by cc must also produce a substring of ss, so all these nodes have transitions labeled cc.

  • Although the nodes that can reach a node qiq_i via a transition labeled cc must form a contiguous segment of the suffix-link tree, this segment need not lie entirely on the suffix path from p0p_0 to the root. In particular, only some of the initial nodes in the segment corresponding to q1q_1 may not be on this suffix path. For example, the node q1q_1 in the figure corresponds to p1′→p2→p3p_1'\rightarrow p_2\rightarrow p_3, where p1′p_1' is not on the suffix path of p0p_0.

    Explanation: Let s2=longest⁡(p2)s_2=\operatorname{longest}(p_2). Then s2+cs_2+c corresponds to q1q_1, but clearly s2+c≠longest⁡(q1)s_2+c\neq\operatorname{longest}(q_1) in the figure, because the latter is longest⁡(p1′)+c\operatorname{longest}(p'_1)+c. This shows that the strings corresponding to the part of q1q_1 cannot be obtained by taking s2s_2 and its suffixes and applying transitions. Conversely, the strings in q2q_2 must be suffixes of s2+cs_2+c, so after removing the final cc they must be suffixes of s2s_2. In other words, the nodes with transitions labeled cc to q2q_2 must lie on the suffix path beginning at p2p_2. This is why only some nodes in the initial segment corresponding to q1q_1 may not lie on the suffix path of p0p_0.

What changes when we add a character to the end of the original string ss—that is, add cc—and construct the corresponding suffix path? The answer is shown below:

Figure: sam suffix path 2

Since node q0q_0 is reached from the node p0p_0 corresponding to the original string ss by a transition labeled cc, it corresponds to the new string s+cs+c. Thus, its suffix path q0→q1′′→q2→q3→t0q_0\rightarrow q_1''\rightarrow q_2\rightarrow q_3\rightarrow t_0 is the newly added suffix path. If a node pip_i on the old suffix path already has a transition labeled cc, the new suffix path must also pass through the node reached by that transition, so we can directly reuse the old node. The new suffix path contains exactly one completely new node, q0q_0, which receives the transitions from the initial nodes on the old suffix path that did not have transitions labeled cc.

Besides these obvious facts, note that the old node q1q_1 is also copied once, or equivalently split into two nodes q1′→q1′′q'_1\rightarrow q_1''. This is because the newly added suffix path overlaps the existing paths only partially: among the strings corresponding to the old node q1q_1, only the shorter ones (those reachable from nodes p2p_2 and p3p_3) appear on the new suffix path, while the longer ones (those reachable from node p1′p_1') do not. Therefore the new suffix path can pass through only part of node q1q_1, which must be split into two nodes to represent this situation. For the same reason, as explained above, the nodes q2q_2 and q3q_3 after q1q_1 cannot be reached by transitions from nodes not on the suffix path of p0p_0, so all strings corresponding to these nodes appear on the new suffix path and no split is needed.

In terms of what states represent in a SAM, each state is an endpos⁡\operatorname{endpos} set. Suppose the new ending position when the string is extended is ii. Then the new node q0q_0 has ending-position set {i}\{i\}, while the split nodes q1′q_1' and q1′′q_1'' correspond to the sets endpos⁡(q1)\operatorname{endpos}(q_1) and endpos⁡(q1)∪{i}\operatorname{endpos}(q_1)\cup\{i\}, respectively. The later nodes q2q_2 and q3q_3 in fact also gain ii in their existing ending-position sets. In other words, although q2q_2, q3q_3, and their associated transitions do not change, the endpos⁡\operatorname{endpos} sets they represent do become larger.

The above describes the most complex and general case (namely case 3 below). In practice, node p1′p'_1 may not exist, so no split is needed (namely case 2 below). To determine this case, it is enough to check whether longest⁡(q1)=longest⁡(p2)+c\operatorname{longest}(q_1)=\operatorname{longest}(p_2)+c, equivalently len⁡(q1)=len⁡(p2)+1\operatorname{len}(q_1)=\operatorname{len}(p_2)+1. It is also possible that none of the nodes on the suffix path of p0p_0 has a transition labeled cc; then only the new node q0q_0 needs to be created (namely case 1 below).

Now that we understand the idea of the new suffix path, let us discuss the concrete steps of incremental construction.

Process

To keep the space complexity linear, we store only the values of len⁡\operatorname{len} and link⁡\operatorname{link} and the transition list of each state. We do not mark terminal states (but we will show later how to assign these marks after constructing the SAM).

Initially, the SAM contains only one state t0t_0, numbered 00 (the other states are numbered 1,2,…1,2,\ldots). For convenience, for state t0t_0, set len⁡(t0)=0\operatorname{len}(t_0)=0 and link⁡(t0)=−1\operatorname{link}(t_0)=-1 (−1-1 denotes a virtual state).

We only need to implement the process of adding a character cc to the current string. The algorithm is as follows:

SAM incremental construction process
  • Let last\textit{last} be the state corresponding to the entire string before character cc is added (initially, set last=0\textit{last}=0; the final step of the algorithm updates last\textit{last}).

  • Create a new state cur\textit{cur} and set len⁡(cur)\operatorname{len}(\textit{cur}) to len⁡(last)+1\operatorname{len}(\textit{last})+1; at this point the value of link⁡(cur)\operatorname{link}(\textit{cur}) is unknown.

  • Now perform the following process: starting from state last\textit{last}, if the current state does not yet have a transition labeled cc, add a transition labeled cc to state cur\textit{cur} and move the current state along its suffix link. If we encounter a state that already has a transition labeled cc, stop and mark this state as pp.

  • Case 1: If no such state pp is found, we have reached the virtual state −1-1; set link⁡(cur)\operatorname{link}(\textit{cur}) to 00 and exit.

  • Suppose now that we have found a state pp with a transition labeled cc. Mark the state reached by this transition as qq. Then either len⁡(p)+1=len⁡(q)\operatorname{len}(p)+1=\operatorname{len}(q) or len⁡(p)+1<len⁡(q)\operatorname{len}(p)+1<\operatorname{len}(q).

  • Case 2: If len⁡(p)+1=len⁡(q)\operatorname{len}(p)+1=\operatorname{len}(q), simply set link⁡(cur)\operatorname{link}(\textit{cur}) to qq and exit.

  • Case 3: Otherwise, the situation is more complicated and we need to clone state qq: create a new state clone\textit{clone}, copying all information from qq except its len⁡\operatorname{len} value (including its suffix link and transitions). Set len⁡(clone)\operatorname{len}(\textit{clone}) to len⁡(p)+1\operatorname{len}(p)+1.

    After cloning, set the suffix link from cur\textit{cur} to clone\textit{clone}, and also set the suffix link from qq to clone\textit{clone}.

    Finally, walk back along suffix links from state pp. Whenever a visited state has a transition to state qq, reconnect that transition to state clone\textit{clone}.

  • After handling all three cases above, update last\textit{last} to state cur\textit{cur}.

If we also want to know which states are terminal states and which are not, we can find all terminal states after constructing the complete SAM for ss. To do so, start from the state corresponding to the entire string (stored in the variable last\textit{last}) and traverse its suffix links until reaching the initial state. Mark every state visited as a terminal state. It is easy to see that this marks exactly all suffixes of ss, and these states are terminal states.

Because we create only one or two new states for each character of ss, the SAM has only a linear number of states. The facts that the SAM also has a linear number of transitions and that the algorithm runs in linear total time will be explained later.

Explanation

We now explain the details of each step of the algorithm and establish its correctness.

Detailed explanation of the algorithm
  • If a transition (p,q)(p,q) satisfies len⁡(p)+1=len⁡(q)\operatorname{len}(p)+1=\operatorname{len}(q), we call it a continuous transition. Otherwise, when len⁡(p)+1<len⁡(q)\operatorname{len}(p)+1<\operatorname{len}(q), it is called a non-continuous transition.

    As can be seen from the algorithm description, continuous and non-continuous transitions are handled differently. Continuous transitions are fixed; we never change them again. In contrast, when a new character is inserted into the string, a non-continuous transition may change (the endpoint of the transition edge may change).

  • To avoid ambiguity, before the current character cc is inserted into the SAM, denote the string by ss.

  • The algorithm starts by creating a new state cur\textit{cur} corresponding to the entire string s+cs+c. The reason for creating a new node is clear. At the same time, we also create a new character and a new equivalence class.

  • After creating a new state, we move from the state corresponding to the entire string ss, namely last\textit{last}, along suffix links. For every state visited, we try to add a transition labeled cc to the new state cur\textit{cur}.

    However, we can add only transitions that do not conflict with existing transitions. Therefore, as soon as we find an existing transition labeled cc, we must stop.

  • The simplest case is that we reach the virtual state −1-1, which means that every suffix of ss has received a transition labeled cc. This also means that character cc has never appeared in ss. Therefore, the suffix link of cur\textit{cur} is state 00.

  • In the second case, we find an existing transition (p,q)(p,q). This means that we are trying to add to the automaton a string x+cx+c that already exists (where xx is a suffix of ss, and the string x+cx+c has already appeared as a substring of ss). Since we assume that the construction of the automaton for ss is correct, we should not add a new transition here.

    The difficulty, however, is determining which state the suffix link from cur\textit{cur} should point to. We need to link it to a state whose longest corresponding string is exactly x+cx+c, so that the state's len⁡\operatorname{len} should be len⁡(p)+1\operatorname{len}(p)+1. Such a state may not exist, namely when len⁡(q)>len⁡(p)+1\operatorname{len}(q)>\operatorname{len}(p)+1. In that case, we must split state qq to create such a state.

  • Of course, if transition (p, q)(p,\,q) is continuous, then len⁡(q)=len⁡(p)+1\operatorname{len}(q)=\operatorname{len}(p)+1. In this case everything is simple: we only need to make the suffix link of cur\textit{cur} point to state qq.

  • Otherwise, the transition is non-continuous, namely len⁡(q)>len⁡(p)+1\operatorname{len}(q)>\operatorname{len}(p)+1. This means that state qq corresponds not only to the suffix s+cs+c of length len⁡(p)+1\operatorname{len}(p)+1, but also to longer substrings of ss. We have no choice but to split state qq into two substates, so the length of the first substate is len⁡(p)+1\operatorname{len}(p)+1.

    How do we split a state? We clone state qq, producing a state clone\textit{clone}, and set len⁡(clone)\operatorname{len}(\textit{clone}) to len⁡(p)+1\operatorname{len}(p)+1. Since we do not want to change paths passing through qq, we copy all transitions of qq to clone\textit{clone}. We also set the suffix link from clone\textit{clone} to the target of qq's suffix link, and set the suffix link of qq to clone\textit{clone}.

    After splitting the state, set the suffix link from cur\textit{cur} to clone\textit{clone}.

    In the final step, we reconnect some transitions that originally pointed to qq to clone\textit{clone}. Which transitions need to be modified? It is enough to reconnect exactly the suffixes of all strings w+cw+c, where ww is the longest string corresponding to state pp. In other words, we continue moving along suffix links from node pp until reaching the virtual state −1-1, or until the current state's transition labeled cc no longer points to state qq.

Linear time complexity

Assume that the alphabet size is a constant, so that searching for a transition, adding a transition, and finding the next transition for one character all take O(1)O(1) time. If the transitions of each node are stored both in an array of length ∣Σ∣\left|\Sigma\right| (for quickly looking up the transition with a given label) and in a dynamic list (for quickly traversing all available transitions), trading space for time, then the time complexity2 of the algorithm is O(n)O(n) and its space complexity is O(n∣Σ∣)O(n\left|\Sigma\right|).

Proof

If we consider the different parts of the algorithm, there are three places whose time complexity is not obviously linear:

  • The first is traversing all suffix links of last\textit{last} and adding transitions labeled cc.
  • The second is copying transitions when state qq is copied to a new state clone\textit{clone}.
  • The third is modifying transitions pointing to qq and reconnecting them to clone\textit{clone}.

We use the fact that the size of a SAM (the number of states and transitions) is linear (the proof that the number of states is linear is the algorithm itself; the proof that the number of transitions is linear is given after the implementation).

Therefore, the total complexity of the first and second parts above is clearly linear, because each operation adds only one new transition to the automaton in amortized terms.

We still need to estimate the total complexity of the third part, in which transitions initially pointing to qq are reconnected to clone\textit{clone}. Let v=longest⁡(p)v=\operatorname{longest}(p), which is a suffix of ss. On each iteration, the length of vv decreases, so the starting position of vv as a suffix of ss necessarily moves to the right. Therefore, the number of times that pp moves along suffix links in the loop is no more than the distance by which the starting position of vv as a suffix of ss moves to the right. Since pp must move at least once to terminate the loop, and since pp is at least the result of moving lastlast once along a suffix link, when the loop terminates the starting position of vv as a suffix of ss is no earlier than that of longest⁡(link⁡(link⁡(last))\operatorname{longest}(\operatorname{link}(\operatorname{link}(\textit{last})). Moreover, when the loop terminates, the starting position of vv as a suffix of ss is exactly the starting position of v+cv+c as a suffix of s+cs+c; as a suffix of s+cs+c, the string v+cv+c is exactly longest⁡(link⁡(link⁡(cur))\operatorname{longest}(\operatorname{link}(\operatorname{link}(\textit{cur})). Since curcur is the updated value of lastlast, the number of moves in the loop is no more than the distance by which longest⁡(link⁡(link⁡(last))\operatorname{longest}(\operatorname{link}(\operatorname{link}(\textit{last})) moves to the right as a suffix of the current string between its old and new values, plus one (the move needed to terminate the loop).

Because the position of the string longest⁡(link⁡(link⁡(last))\operatorname{longest}(\operatorname{link}(\operatorname{link}(\textit{last})) as a suffix of the current string increases monotonically throughout the entire SAM construction3, its total movement is at most nn. This shows that the number of iterations in the loop modifying transitions pointing to qq is at most 2n2n. This is exactly what we needed to prove.

Of course, if the alphabet size is not constant, the time complexity of a SAM is not linear. The transitions leaving a node must be stored in a balanced tree supporting fast lookup and insertion. Thus, if Σ\Sigma is the alphabet and ∣Σ∣\left|\Sigma\right| is its size, the asymptotic time complexity of the algorithm is O(nlog⁡∣Σ∣)O(n\log\left|\Sigma\right|) and the space complexity is O(n)O(n).

Implementation

First, we implement a data structure that stores all information about a transition. If necessary, you can add a terminal marker or some other information here. We store the transition list in a map, allowing us to process the whole string in total O(n)O(n) space and O(nlog⁡∣Σ∣)O(n\log\left|\Sigma\right|) time. Of course, when the alphabet size is a small constant KK (for example, 26), it is more convenient to declare next as int[K].

struct state {
  int len, link;
  std::map<char, int> next;
};

The SAM itself will be stored in an array of state structures. We record the current automaton size sz and the variable last, the state corresponding to the entire current string.

constexpr int MAXLEN = 100000;
state st[MAXLEN * 2];
int sz, last;

We define a function to initialize the SAM (creating a SAM containing only the initial state).

void sam_init() {
  st[0].len = 0;
  st[0].link = -1;
  sz++;
  last = 0;
}

Finally, we give the implementation of the main function: add a character to the end of the current string and build the automaton accordingly on top of the previous one.

Implementation
void sam_extend(char c) {
  int cur = sz++;
  st[cur].len = st[last].len + 1;
  int p = last;
  while (p != -1 && !st[p].next.count(c)) {
    st[p].next[c] = cur;
    p = st[p].link;
  }
  if (p == -1) {
    st[cur].link = 0;
  } else {
    int q = st[p].next[c];
    if (st[p].len + 1 == st[q].len) {
      st[cur].link = q;
    } else {
      int clone = sz++;
      st[clone].len = st[p].len + 1;
      st[clone].next = st[q].next;
      st[clone].link = st[q].link;
      while (p != -1 && st[p].next[c] == q) {
        st[p].next[c] = clone;
        p = st[p].link;
      }
      st[q].link = st[cur].link = clone;
    }
  }
  last = cur;
}

As mentioned earlier, if you trade memory for time (space complexity O(n∣Σ∣)O(n\left|\Sigma\right|), where ∣Σ∣\left|\Sigma\right| is the alphabet size), you can construct a SAM for an alphabet of arbitrary size in O(n)O(n) time2. However, you need to store for each state an array of size ∣Σ∣\left|\Sigma\right| (for quickly finding the corresponding transition by character) and a list containing all available transitions (for quickly traversing all available transitions).

More properties

Number of states

For a string of length nn, denoted ss, the number of states in its SAM does not exceed 2n−12n-1 (assuming n≥2n\ge 2).

Proof

The algorithm itself proves this claim. At the beginning the automaton contains one state; the first and second iterations create only one node each, and each of the remaining n−2n-2 steps creates at most 22 states.

We can also prove this bound without using the algorithm. Recall that the number of states equals the number of distinct endpos⁡\operatorname{endpos} sets. These endpos⁡\operatorname{endpos} sets form a tree (the endpos⁡\operatorname{endpos} set of a parent contains the endpos⁡\operatorname{endpos} set of a child). Consider slightly modifying this tree: whenever it has an internal node with only one child (which means that the child's set omits at least one position from the parent's set), create a child whose set contains those omitted positions. In the end, we obtain a tree in which every internal node has degree greater than one, and the number of leaves is at most nn. Such a tree has at most 2n−12n-1 nodes, so the number of distinct endpos⁡\operatorname{endpos} sets in the original tree is also at most 2n−12n-1.

The string abbb⋯bbb\mathtt{abbb} \cdots \mathtt{bbb} reaches this upper bound: from the third iteration onward, every iteration splits a state, ultimately producing exactly 2n−12n-1 states.

Number of transitions

For a string of length nn, denoted ss, the number of transitions in its SAM does not exceed 3n−43n-4 (assuming n≥3n\ge 3).

Proof

We first estimate the number of continuous transitions. Consider the spanning tree formed by the longest paths from state t0t_0 to all states in the automaton. The spanning tree contains only continuous edges, so its number of edges is less than the number of states, and hence no more than 2n−22n-2.

We now estimate the number of non-continuous transitions. Let the current non-continuous transition be (p, q)(p,\,q), with character cc. Consider its corresponding string u+c+wu+c+w, where uu corresponds to a longest path from the initial state to pp, and ww corresponds to a longest path from qq to any terminal state. On the one hand, the strings of the form u+c+wu+c+w corresponding to different non-continuous transitions are distinct (because uu and ww consist only of continuous transitions). On the other hand, by the definition of terminal states, every string of the form u+c+wu+c+w is a suffix of the whole string ss. Since ss has nn nonempty suffixes, and none of the strings of the form u+c+wu+c+w is ss itself (because the whole string consists only of continuous transitions), the total number of non-continuous transitions is at most n−1n-1.

Adding the two estimates above gives the upper bound 3n−33n-3. However, the maximum number of states can occur only in cases like abbb⋯bbb\mathtt{abbb} \cdots \mathtt{bbb}, and in that case the number of transitions is clearly less than 3n−33n-3.

Therefore we obtain the tighter upper bound 3n−43n-4 for the number of SAM transitions. The string abbb⋯bbbc\mathtt{abbb} \cdots \mathtt{bbbc} reaches this bound.

Although SAM construction is intended to obtain information about its states and transitions, the suffix links link⁡\operatorname{link} recorded during construction and the corresponding longest-substring lengths len⁡\operatorname{len} are often more important in applications than the SAM transitions, and can even be used independently of the transitions.

During SAM construction, the value of the last\textit{last} state must be updated. It corresponds to the string before (and after) each character is added, that is, every prefix of ss. Let the state corresponding to the ii-th prefix be viv_i; then we obtain the states v0,v1,⋯ ,vn−1v_0,v_1,\cdots,v_{n-1}, totaling nn states. In addition, define the initial state t0t_0 as v−1v_{-1}, corresponding to the empty prefix. We will refer to these states as "prefix nodes" for now.

As mentioned in Lemma 4, all states and suffix links form a rooted tree with root t0t_0. This tree is also called the suffix-link tree (Chinese OI contestants often call it the parent tree). It records all suffixes of all prefixes of the string, that is, all substrings.

The suffix-link tree has the following properties:

  • The string corresponding to an ancestor node is always a suffix of the string corresponding to a descendant node.
  • The endpos⁡\operatorname{endpos} set at each node is exactly the set of all indices ii of "prefix nodes" viv_i in its subtree.
  • The endpos⁡\operatorname{endpos} set of an ancestor in the suffix-link tree always strictly contains the endpos⁡\operatorname{endpos} set of a descendant.
  • The len⁡\operatorname{len} value at each node is the length of the longest common suffix of all prefixes corresponding to the "prefix nodes" viv_i in its subtree.
  • Except for the root t0t_0, the number of distinct substrings corresponding to each node is its len⁡\operatorname{len} value minus the len⁡\operatorname{len} value of its parent, namely len⁡(v)−len⁡(link⁡(v))\operatorname{len}(v)-\operatorname{len}(\operatorname{link}(v)).

These properties have many applications. For example, the longest common suffix of the ii-th and jj-th prefixes is the string corresponding to the longest string of the LCA of viv_i and vjv_j.

Finally, the suffix-link tree built for a string ss has the same structure as the suffix tree built for its reverse sRs_R. This is often used to construct a suffix tree offline.

Applications

Let us look at some problems that can be solved using a SAM. For simplicity, assume that the alphabet size kk is a constant. This allows us to treat the complexity of adding and traversing a character as constant.

Checking whether a string occurs

Problem

Given a text string TT and several pattern strings PP, determine whether PP occurs as a substring of TT.

Solution

In time O(∣T∣)O(\left|T\right|), we construct a suffix automaton for the text string TT. To check whether a pattern string PP occurs in TT, we start at t0t_0 and follow transitions according to the characters of PP. If at some point no transition is available, then PP is not a substring of TT. If we process the entire string PP this way, then the pattern occurs in TT.

For each string PP, the time complexity of the algorithm is O(∣P∣)O(\left|P\right|). In addition, this algorithm finds the maximum prefix length of PP that occurs in the text string.

Number of distinct substrings

Problem

Given a string SS, count the number of distinct substrings.

Solution 1

Construct a suffix automaton for the string SS.

Every substring of SS corresponds to some path in the automaton. Therefore, the number of distinct substrings equals the number of distinct paths starting at t0t_0 in the automaton.

Since the SAM is a directed acyclic graph, the number of paths can be computed by dynamic programming. Let dvd_{v} be the number of paths starting from state vv (including the path of length zero). Then we have the recurrence:

dv=1+∑w:(v,w,c)∈DAWGdwd_{v}=1+\sum_{w:(v,w,c)\in DAWG}d_{w}

That is, dvd_{v} is the sum over the endpoints of all transitions from vv; the triple (v,w,c)(v,w,c) in DAWGDAWG means that the suffix automaton has a transition from vv to ww via cc.

Thus, the number of distinct substrings is dt0−1d_{t_0}-1 (because the empty substring must be removed).

The total time complexity is O(∣S∣)O(\left|S\right|).

Solution 2

Another method is to use the information in the suffix-link tree after constructing the suffix automaton. The number of substrings corresponding to each node is len⁡(v)−len⁡(link⁡(v))\operatorname{len}(v)-\operatorname{len}(\operatorname{link}(v)); summing this over all automaton nodes gives the answer.

The total time complexity is still O(∣S∣)O(\left|S\right|).

Example problems: 【Suffix Automaton (Template)】, SDOI2016 Generating Magic

Total length of all distinct substrings

Problem

Given a string SS, calculate the total length of all distinct substrings.

Solution 1

The solution to this problem is similar to that of the previous problem, except that we now need to perform dynamic programming in two parts: the number dvd_{v} of distinct substrings and their total length ansvans_{v}.

We introduced how to calculate dvd_{v} in the previous problem. The value of ansvans_{v} can be calculated by the following recurrence:

ansv=∑w:(v,w,c)∈DAWGdw+answans_{v}=\sum_{w:(v,w,c)\in DAWG}d_{w}+ans_{w}

We take the answer for each adjacent node ww and add dwd_{w}, because every substring starting from state vv gains one character.

The time complexity of the algorithm is still O(∣S∣)O(\left|S\right|).

Solution 2

We can likewise use information from the suffix-link tree. The total lengths of all suffixes of the longest substring corresponding to each node are

len⁡(v)×(len⁡(v)+1)2,\dfrac{\operatorname{len}(v)\times (\operatorname{len}(v)+1)}{2},

and subtracting the corresponding value for its link⁡\operatorname{link} node gives the net contribution of that node. Summing over all nodes of the automaton is sufficient.

The total time complexity is still O(∣S∣)O(\left|S\right|).

The k-th Largest Substring in Lexicographic Order

Problem

Given a string SS and multiple queries. For each query, given KiK_i, find the largest substring of SS that is the KiK_i-th in lexicographic order.

Solution

The idea for solving this problem can be developed from the ideas used for the first two problems. The kk-th largest substring in lexicographic order corresponds to the kk-th largest path in the SAM. Therefore, after calculating the number of paths from each state, we can easily find the kk-th largest path starting from the root of the SAM.

The preprocessing time complexity is O(∣S∣)O(\left|S\right|), and the complexity of a single query is O(∣ans∣⋅∣Σ∣)O(\left|ans\right|\cdot\left|\Sigma\right|), where ansans is the query answer and ∣Σ∣\left|\Sigma\right| is the size of the character set.

Note

Although this is a classic suffix-automaton problem, suffix arrays are actually the most convenient choice because the problem involves lexicographic order.

Example problems: SPOJ - SUBLEX, TJOI2015 String Theory

Minimum Cyclic Shift

Problem

Given a string SS, find its lexicographically smallest cyclic shift.

Solution

It is easy to see that S+SS+S contains all cyclic shifts of SS as substrings.

Thus the problem reduces to finding, in the suffix automaton of S+SS+S, the smallest path of length ∣S∣\left|S\right|. This can be done trivially: starting from the initial state, greedily follow the smallest character.

The total time complexity is O(∣S∣)O(\left|S\right|).

Occurrence Count

Problem

Given a text string TT and multiple queries, each providing a pattern string PP, determine how many times the pattern string PP occurs as a substring in TT.

Solution 1

Using information from the suffix-link tree, we can preprocess the size of the endpos⁡\operatorname{endpos} set of every node with a DFS.

Initialize the set size of every “prefix node” to 11, and that of every non-“prefix node” to 00. Then, while traversing upward along suffix links from the bottom, add the set sizes of all child nodes to each parent node (do not omit the parent's own initial value). The resulting value at each node is the size of its endpos⁡\operatorname{endpos} set. The reason the set sizes of different child nodes can be added directly is that the same viv_i appears in only one subtree, so the addition does not duplicate anything.

During a query, find the node corresponding to the pattern string PP in the automaton. If it exists, the answer is the size of that node's endpos⁡\operatorname{endpos} set; otherwise, the answer is 00.

The preprocessing time complexity is O(∣T∣)O(|T|), and the time complexity of a single query is O(∣P∣)O(|P|).

Solution 2

Construct a suffix automaton for the text string TT.

Then preprocess, for every state vv in the automaton, a value cntvcnt_{v} equal to the size of the endpos⁡(v)\operatorname{endpos}(v) set. In fact, all substrings corresponding to the same state vv occur the same number of times in the text string TT; this is the number of positions in the endpos⁡\operatorname{endpos} set.

However, we cannot explicitly construct the endpos⁡\operatorname{endpos} sets, so we only consider their sizes cntcnt.

To calculate these values, perform the following operations. For each state, if it was not created by copying (and is not the initial state t0t_0), initialize its cntcnt to 1. Then traverse all states in decreasing order of their length len⁡\operatorname{len} and add the current value of cntvcnt_{v} to the state pointed to by its suffix link:

cntlink⁡(v)+=cntvcnt_{\operatorname{link}(v)}+=cnt_{v}

After this, the answer for every state is correct.

Why is this correct? There are exactly ∣T∣\left|T\right| states that were not obtained by copying, and the first ii of them were created when the first ii characters were inserted. Thus, for each such state, when it is processed, we count the number of positions corresponding to it. We therefore initially set the cntcnt of these states to 11 and the cntcnt of all other states to 00.

Next, for each vv, perform the following operation: cntlink⁡(v)+=cntvcnt_{\operatorname{link}(v)}+=cnt_{v}. Its meaning is that if a string vv occurs cntvcnt_{v} times, all of its suffixes end at exactly the same positions, and therefore also occur cntvcnt_{v} times.

Why do we not count any position more than once in this process? Because we add the positions of one state to only one other state, so a state cannot point its positions to another state in two different ways.

Therefore, in O(∣T∣)O(\left|T\right|) time, all state values cntcnt can be calculated.

Finally, answering a query only requires looking up cnttcnt_{t}, where tt is the state corresponding to the pattern string. If the pattern string does not exist, the answer is 00. The time complexity of a single query is O(∣P∣)O(\left|P\right|).

First Occurrence Position

Problem

Given a text string TT and multiple queries, return the position of the first occurrence of the string PP in TT, that is, the starting position of PP.

Solution 1

Using information from the suffix-link tree, we can preprocess the minimum value in the endpos⁡\operatorname{endpos} set of every node with a DFS.

Initialize every “prefix node” viv_i to ii, and every non-“prefix node” to ∞\infty. Then, while traversing upward along suffix links from the bottom, compare each parent's value with the values of all its child nodes and take the minimum (do not omit the parent's own initial value). The resulting value at each node is the minimum value in its endpos⁡\operatorname{endpos} set.

During a query, find the node corresponding to the pattern string PP in the automaton. If it exists, the answer is its value minus ∣P∣−1|P|-1; otherwise, the answer does not exist.

The preprocessing time complexity is O(∣T∣)O(|T|), and the time complexity of a single query is O(∣P∣)O(|P|).

Solution 2

Construct a suffix automaton. For every state in the SAM, preprocess the position firstpos⁡\operatorname{firstpos}. That is, for each state vv, find the position where a string represented by this state first occurs, firstpos⁡[v]\operatorname{firstpos}[v]. In other words, find the minimum element of each endpos⁡\operatorname{endpos} set (although we obviously cannot maintain all endpos⁡\operatorname{endpos} sets explicitly).

To maintain these firstpos⁡\operatorname{firstpos} positions, extend the function sam_extend(). When creating a new state cur\textit{cur}, set

firstpos⁡(cur)=len⁡(cur)−1.\operatorname{firstpos}(\textit{cur})=\operatorname{len}(\textit{cur})-1.

When copying node qq to clone\textit{clone}, set

firstpos⁡(clone)=firstpos⁡(q).\operatorname{firstpos}(\textit{clone})=\operatorname{firstpos}(q).

(The only other possible value, firstpos⁡(cur)\operatorname{firstpos}(\textit{cur}), is clearly too large.)

The answer to a query is then firstpos⁡(t)−∣P∣+1\operatorname{firstpos}(t)-\left|P\right|+1, where tt is the state corresponding to the string PP. A single query takes only O(∣P∣)O(\left|P\right|) time.

All Occurrence Positions

Problem

The problem is the same as above; this time, given the text string TT, find all positions where the pattern string PP occurs.

Solution 1

After finding the node corresponding to the pattern string PP, traverse its subtree using information from the suffix-link tree, and output a position whenever a terminal node is found.

The complexity of a single query is O(∣P∣)+O(answer(P))O(|P|)+O(\textit{answer}(P)), where answer(P)\textit{answer}(P) is the answer to the query. As in the proof that the number of states is linear, the size of a subtree in the suffix-link tree is no more than twice the size of that node's endpos⁡\operatorname{endpos} set, so the subtree traversal has complexity O(answer(P))O(\textit{answer}(P)).

Solution 2

Again, construct a suffix automaton for the text string TT. As in the previous problem, calculate firstpos⁡\operatorname{firstpos} for every state.

If tt is the state corresponding to the pattern string PP, then firstpos⁡(t)\operatorname{firstpos}(t) is clearly one of the answers. We have already found the state corresponding to PP in the automaton. Which other positions do we need to find? They are precisely those corresponding to strings that have PP as a suffix. In other words, we need to find all states from which state tt can be reached by suffix links.

Therefore, to solve this problem, we need to store, for each state, a list of suffix links pointing to it. The query answer consists, for every state found by performing DFS or BFS from state tt using only reversed suffix links, of its firstpos⁡\operatorname{firstpos} value.

The preprocessing complexity is O(∣T∣)O(|T|), and the complexity of a single query is O(∣P∣+answer(P))O(|P|+\textit{answer}(P)).

We never visit a state more than once, because each state has only one suffix link, so there cannot be two different paths to the same state.

We only need to consider different states that may have the same firstpos⁡\operatorname{firstpos} value. This happens only when one state is copied from another. However, this does not affect the complexity analysis. As in the proof that the number of states is linear, the number of such states whose suffix is PP is no more than 2answer(P)2\textit{answer}(P).

Moreover, we can remove duplicate positions by ignoring the firstpos⁡\operatorname{firstpos} values of copied nodes. In fact, for a state, if it can be reached through the copied state, it can also be reached through the original state. Thus, if we record a marker is_clone for each state to indicate whether it was copied, we can simply ignore copied states and output the firstposfirstpos values of all other states.

The following is a rough implementation:

struct state {
  bool is_clone;
  int first_pos;
  std::vector<int> inv_link;
  // some other variables
};

// After constructing the SAM
for (int v = 1; v < sz; v++) st[st[v].link].inv_link.push_back(v);

// Output all occurrence positions
void output_all_occurrences(int v, int P_length) {
  if (!st[v].is_clone) cout << st[v].first_pos - P_length + 1 << endl;
  for (int u : st[v].inv_link) output_all_occurrences(u, P_length);
}

Shortest String That Does Not Occur

Problem

Given a string SS and a particular character set, find the shortest string that does not occur in SS.

Solution

Perform dynamic programming on the suffix automaton of string SS.

Suppose that we have already processed part of the substring and are currently at state vv. Let the minimum number of characters that must be added at state vv to obtain a transition that does not occur be dvd_v.

Computing dvd_{v} is simple. If there is no transition using at least one character from the character set, then dv=1d_{v}=1. Otherwise, adding one character is not enough, and we take the minimum over all transitions:

dv=1+min⁡w:(v,w,c)∈SAMdwd_{v}=1+\min_{w:(v,w,c)\in SAM}d_{w}

The answer is dt0d_{t_0}, and the string can be reconstructed backward using the computed array dd.

Longest Common Substring of Two Strings

Problem

Given two strings SS and TT, find the longest string that occurs as a substring in both SS and TT; call this string XX.

Solution

Construct a suffix automaton for string SS.

We now process string TT. For every prefix, find the longest suffix of that prefix that occurs in SS. In other words, for every position in TT, find the length of the longest common substring of SS and TT ending at that position.

To do this, use two variables, current state vv and current length ll. They describe the currently matched part: its length and the state corresponding to it.

Initially, v=t0v=t_0 and l=0l=0, meaning that the match is the empty string.

We now describe how to add a character TiT_{i} and recompute the answer:

  • If there is a transition from vv using character TiT_{i}, simply follow it and increment ll.

  • If no such transition exists, shorten the current match, which means following a suffix link:

    v=link⁡(v)v=\operatorname{link}(v)

    At the same time, shorten the current length. We clearly need to set ll to len⁡(v)\operatorname{len}(v), because the longest string corresponding to the state reached through this suffix link is a substring.

  • If there is still no transition using this character, continue following suffix links and decreasing ll until a transition is found or the virtual state −1-1 is reached. The latter means that character TiT_{i} does not occur in SS at all, so set v=l=0v=l=0.

Clearly, the answer is the maximum of all values of ll.

The time complexity of this part is O(∣T∣)O(\left|T\right|), because each move either increases ll by one or moves along suffix links several times, decreasing ll on each move.

Code:

string lcs(const string &S, const string &T) {
  sam_init();
  for (int i = 0; i < S.size(); i++) sam_extend(S[i]);

  int v = 0, l = 0, best = 0, bestpos = 0;
  for (int i = 0; i < T.size(); i++) {
    while (v && !st[v].next.count(T[i])) {
      v = st[v].link;
      l = st[v].length;
    }
    if (st[v].next.count(T[i])) {
      v = st[v].next[T[i]];
      l++;
    }
    if (l > best) {
      best = l;
      bestpos = i;
    }
  }
  return T.substr(bestpos - best + 1, best);
}

Example problem: SPOJ Longest Common Substring

Longest Common Substring of Multiple Strings

Problem

Given kk strings SiS_i, find their longest common substring, that is, a string XX that occurs as a substring in every string.

Solution 1

Concatenate all the substrings into one longer string TT, separating each string with a special character DiD_i (one character for each string):

T=S1+D1+S2+D2+⋯+Sk+Dk.T=S_1+D_1+S_2+D_2+\cdots+S_k+D_k.

Then construct a suffix automaton for string TT.

We now need to find, in the automaton, a string that occurs in every string SiS_i. The added special characters are useful here. If SjS_j contains a substring XX, then from the node corresponding to substring XX, denoted tt, there must be a path to DjD_j that passes through none of the other special characters D1,⋯ ,Dj−1,Dj+1,⋯ ,DkD_1,\cdots,D_{j-1},D_{j+1},\cdots,D_k. For a common substring XX, such a path must exist for every special character DjD_j.

Thus we need to calculate reachability: for every character DiD_i and every state vv in the automaton, determine whether such a path exists. This can be computed easily with DFS or BFS and dynamic programming. The answer is the longest longest⁡(v)\operatorname{longest}(v) among the states that can reach all special characters.

Solution 2

Without loss of generality, let S1S_1 be the shortest string and construct a SAM for it. Use the algorithm for the longest common substring of two strings to calculate the longest common substring length between each remaining string and S1S_1. During matching, each time a character from a string SjS_j is matched, we move accordingly in the SAM, so we can directly record the length of the longest substring of SjS_j that each SAM state can match during the matching process.

During matching, whenever a SAM state is reached, all its ancestors in the suffix-link tree are necessarily matched at the same time, but the matching-length information for the ancestors is not updated. Therefore, after matching string SjS_j, update along suffix links from bottom to top, propagating the longest matched-substring information from child nodes to parent nodes. Be careful that the longest matching length recorded at a parent cannot exceed its own len⁡\operatorname{len} value. This yields the length of the longest substring that each state in the SAM of S1S_1 can actually match in SjS_j.

Finally, match every S2,⋯ ,SkS_2,\cdots,S_k once, take the minimum over the actually matched lengths recorded at each SAM state, and obtain the length of the longest common substring of S2,⋯ ,SkS_2,\cdots,S_k that each state can actually match. Then traverse all SAM states and take the maximum; this is the length of the longest common substring of the kk strings.

The time complexity is O(∑i∣Si∣)O(\sum_i |S_i|). Although the SAM of S1S_1 is traversed kk times, ∣S1∣|S_1| is the smallest, so k∣S1∣≤∑i∣Si∣k|S_1|\le \sum_i |S_i|; the dominant term in the complexity is still the matching process that traverses all substrings.

Example problem: SPOJ Longest Common Substring II

Exercises

Here are some of the earliest works related to SAM:

  • A. Blumer, J. Blumer, A. Ehrenfeucht, D. Haussler, R. McConnell. Linear Size Finite Automata for the Set of All Subwords of a Word. An Outline of Results. [1983]
  • A. Blumer, J. Blumer, A. Ehrenfeucht, D. Haussler. The Smallest Automaton Recognizing the Subwords of a Text. [1984]
  • Maxime Crochemore. Optimal Factor Transducers. [1985]
  • Maxime Crochemore. Transducers and Repetitions. [1986]
  • A. Nerode. Linear automaton transformations. [1958]

This topic can also be found in newer resources and in many books on string algorithms:

  • Maxime Crochemore, Rytter Wowjcieh. Jewels of Stringology. [2002]
  • Bill Smyth. Computing Patterns in Strings. [2003]
  • Bill Smith. Methods and algorithms of calculations on lines. [2006]

Additional resources:

This page was mainly translated from the article Суффиксный автомат and its English translation Suffix Automaton. The Russian version is licensed under Public Domain + Leave a Link; the English version is licensed under CC-BY-SA 4.0.

Footnotes

  1. The reason for taking every state to be an endpos⁡\operatorname{endpos} equivalence class is essentially the Myhill–Nerode theorem mentioned in this section. In simple terms, if two strings tt and uu have different endpos⁡\operatorname{endpos} sets, they cannot correspond to the same state of the SAM: the paths from the same state to terminal states are always the same, which means that the ways of appending characters to the ends of tt and uu to reach the end of ss are also the same; this is precisely what says that tt and uu end at the same positions in ss. Conversely, as long as two strings tt and uu have the same endpos⁡\operatorname{endpos} set, they can correspond to the same state of the SAM. The proof that this is possible is the content of Nerode's theorem and is not discussed here. Nevertheless, the discussion at least makes it plausible that placing strings with the same endpos⁡\operatorname{endpos} set in one state produces a minimal SAM, because further merging of nodes is impossible. ↩

  2. If we do not additionally use a list to record the available transitions of the current state, but use an array storing all possible transitions (whether or not they exist) and copy it directly when copying a node, the time complexity is also O(n∣Σ∣)O(n\left|\Sigma\right|). ↩ ↩2

  3. What the main text does not explain here is whether the position of longest⁡(link⁡(link⁡(last)))\operatorname{longest}(\operatorname{link}(\operatorname{link}(\textit{last}))) is also monotonically nondecreasing in the first and second cases. The first case is easy to verify, because after the update, longest⁡(link⁡(link⁡(last)))\operatorname{longest}(\operatorname{link}(\operatorname{link}(\textit{last}))) is the empty string, whose start and end positions are at the end of string ss. In the second case, the transition is continuous, which means longest⁡(q)=longest⁡(p)+c\operatorname{longest}(q) = \operatorname{longest}(p)+c. However, adding a new character to the end of a substring only makes it harder for that substring to occur in the string. In other words, when the set of end positions of the suffix of longest⁡(p)\operatorname{longest}(p) of length len⁡(link⁡(p))\operatorname{len}(\operatorname{link}(p)) strictly contains endpos⁡(p)\operatorname{endpos}(p), the set of end positions of the suffix of longest⁡(q)\operatorname{longest}(q) of length len⁡(link⁡(p))+1\operatorname{len}(\operatorname{link}(p))+1 may still be the same as endpos⁡(q)\operatorname{endpos}(q). Hence len⁡(link⁡(q))<len⁡(link⁡(p))+1\operatorname{len}(\operatorname{link}(q))<\operatorname{len}(\operatorname{link}(p))+1, which means that the starting position of longest⁡(link⁡(p))\operatorname{longest}(\operatorname{link}(p)) as a suffix of ss is no later than the starting position of longest⁡(link⁡(q))\operatorname{longest}(\operatorname{link}(q)) as a suffix of s+cs+c. When a state pp for which a transition via cc exists is found, at least one move must have been made, which means that the starting position of longest⁡(link⁡(p))\operatorname{longest}(\operatorname{link}(p)) as a suffix of ss is no earlier than the starting position of longest⁡(link⁡(link⁡(last)))\operatorname{longest}(\operatorname{link}(\operatorname{link}(\textit{last}))) as a suffix of ss. Finally, longest⁡(link⁡(q))=longest⁡(link⁡(link⁡(cur)))\operatorname{longest}(\operatorname{link}(q))=\operatorname{longest}(\operatorname{link}(\operatorname{link}(\textit{cur}))). This shows that, in the second case, the position of longest⁡(link⁡(link⁡(last)))\operatorname{longest}(\operatorname{link}(\operatorname{link}(\textit{last}))) is also monotonically increasing. ↩

This content comes from OI Wiki: Suffix Automaton (SAM). See the original for authors and revision history. AIOJ has adapted the formatting, in-site links, and resource paths.

Content is shared under CC BY-SA 4.0 and SATA license; additional terms are governed by the original notice. Thanks to the OI Wiki authors and contributors; per the SATA terms, please visit the OI Wiki repository and star it to show your support.