Abstract 1 Introduction 2 Preliminaries 3 A Smallest Suffixient Set: A New Characterization and Proof 4 Computing 𝒔𝑨: A One-Pass Algorithm with One-Step Look-ahead References Appendix A The Pseudocode for Computing 𝚠(𝒊) Appendix B The Pseudocode for Computing 𝚋(𝒊) Appendix C The Pseudocode and an Example for Computing Suffixient Arrays

Constructing Suffixient Arrays Revisited

Paola Bonizzoni ORCID Department of Computer Science, University of Milano-Bicocca, Italy    Younan Gao ORCID Department of Computer Science, University of Milano-Bicocca, Italy    Brian Riccardi ORCID Department of Computer Science, University of Milano-Bicocca, Italy
Abstract

Recently, Cenzato et al. proposed a new text index, called the suffixient array, which is a subset of the suffix array and supports locating a single pattern occurrence or finding its maximal exact matches (MEMs), assuming random access to the input text T[1..n] is available. They show that, given the suffix array, the longest common prefix array, and the Burrows–Wheeler transform (𝙱𝚆𝚃) of the reverse of T[1..n] over an alphabet {1,,σ}, a suffixient array can be constructed in linear time. However, their construction algorithms require multiple scans of these arrays. When restricted to a single pass over the arrays, they present an alternative construction algorithm running in 𝒪(n+r¯logσ) time, where r¯ is the number of runs in the 𝙱𝚆𝚃 of the reversed text. In this paper, we present a new one-pass algorithm that constructs a suffixient array in linear time under the standard RAM model.

Keywords and phrases:
Suffixient set, suffixient array, right-maximal substring, linear-time algorithm
Copyright and License:
[Uncaptioned image] © Paola Bonizzoni, Younan Gao, and Brian Riccardi; licensed under Creative Commons License CC-BY 4.0
2012 ACM Subject Classification:
Theory of computation Data structures design and analysis
Funding:
All authors have received funding from the European Union’s Horizon 2020 research and innovation programme under the Marie Skłodowska-Curie grant agreement PANGAIA No. 872539, as well as from grant MIUR 2022YRB97K (PINC, Pangenome Informatics: From Theory to Applications), funded by the European Union under the NextGenerationEU programme, Mission 4.
Editors:
Philip Bille and Nicola Prezza

1 Introduction

Pattern matching is a fundamental problem with applications ranging from text processing to computational biology. The suffix array [12] is a textbook data structure that supports efficient pattern matching by storing the starting positions of all suffixes of a text in lexicographical order. Together with auxiliary structures such as the longest common prefix array [12], the suffix array enables fast pattern searches and serves as the basis of many full-text indexes. However, the limitations of suffix arrays become apparent when dealing with massive texts, such as collections of human genomes. Since a suffix array requires linear space in the text length, storing and processing it for such datasets is often infeasible in practice.

At the same time, genomic data exhibits a high degree of similarity and repetitiveness: genomes from different individuals share the vast majority of their sequences, with variations occurring only at relatively sparse locations. This strong repetitiveness suggests that designing space-efficient indexes whose space usage scales with intrinsic measures of repetitiveness, rather than with the raw text length n, is both natural and highly desirable.

Suffixient arrays [5] were recently proposed as a space-efficient alternative to suffix arrays for indexing highly similar texts such as genome sequences. Intuitively, a suffixient array can be viewed as a carefully selected subset of the prefix array, the symmetric counterpart of the suffix array. Its definition is grounded in the notion of right-maximal substrings, that is, substrings that occur in the text and can be extended to the right by at least two distinct characters. Such substrings correspond exactly to the internal nodes of the suffix tree. The suffixient array captures these branching points by including prefixes of the text that cover all one-character right-maximal extensions, ensuring that the structure is sufficient to represent all essential distinctions induced by right-maximal extensions.

Despite its reduced size, a suffixient array supports a specialized set of queries, provided random access to the underlying text: it can be used to either locate a single occurrence of a given pattern or to find maximal exact matches (MEMs), which are of particular importance in bioinformatics applications [5].

In this paper, we focus on the efficient construction of suffixient arrays. Unlike suffix arrays, which are uniquely determined for a given text, a text may admit multiple valid suffixient arrays. Our goal is to compute any one of them.

Related Work.

Recently, Navarro et al. [13] introduced an online construction algorithm that computes a minimum-size suffixient set – the set of indices forming a suffixient array – directly from the text. Their approach adapts Ukkonen’s algorithm [15], originally designed for the online construction of the suffix tree. Specifically, their method incrementally maintains a minimum-size suffixient set for the prefix T[1..i] as the text is scanned left to right.

The algorithm runs in linear time over an alphabet of size σ under the trans-dichotomous RAM model [8], but requires 𝒪(nlogσ) time in the standard RAM model [2]. Its working space is comparable to that of a suffix tree built over the text, which is known to have a large memory footprint in practice – up to 20 bytes per input character in the worst case [10]. Navarro et al. also demonstrated that a minimum-size suffixient set can be constructed incrementally while scanning the text from right to left.

Cenzato et al. [5] addressed the problem of constructing a minimum-size suffixient set given the suffix array 𝚂𝙰[1..n], the Burrows–Wheeler transform (BWT) [4] array 𝙱𝚆𝚃[1..n], and the longest common prefix array 𝙻𝙲𝙿[1..n] of the reversed text. They presented two linear-time algorithms that require multiple passes over these arrays, resulting in a working space of Ω(n) words. They also proposed a construction algorithm that avoids storing the full 𝚂𝙰, 𝙱𝚆𝚃, and 𝙻𝙲𝙿 arrays and processes them in a single pass. This algorithm runs in 𝒪(n+r¯logσ) time, where r¯ denotes the number of runs in the BWT, and uses only 𝒪(σ) words of working space. When computing a suffixient array, the running time remains unchanged, while the working space increases from 𝒪(σ) to 𝒪(σ+χ)𝒪(χ) words, where χ denotes the size of the suffixient array and σχ [5].

Using the technique of prefix-free parsing (PFP) [3, 9, 14], the entries of the 𝚂𝙰, 𝙱𝚆𝚃, and 𝙻𝙲𝙿 arrays can be generated in a left-to-right streaming fashion while using compressed space. By combining their one-pass algorithm with PFP, Cenzato et al. [5] were able to construct a suffixient array in compressed space.

Our Results.

In this paper, we revisit the problem of constructing a suffixient array in the standard RAM model, focusing on the specific setting introduced by Cenzato et al. [5]. Our main contribution is a linear-time streaming algorithm that constructs a suffixient array in n iterations by processing the 𝚂𝙰, 𝙻𝙲𝙿, and 𝙱𝚆𝚃 arrays of the reversed text T[1..n] in a single pass with one-step look-ahead. The efficiency of the algorithm relies on two key mechanisms.

Dynamic Candidate Management. At each iteration i, we maintain a set of candidate positions jtext for the suffixient array in a doubly-linked list (𝚛𝚘𝚠𝙻𝚒𝚜𝚝). The list has size at most σ and is kept sorted in non-increasing order by the weight 𝚠(j). Here, 𝚠(j) denotes the maximum length of a right-maximal substring that is a suffix of T[1..jtext1], where j=𝚂𝙰1[njtext+1]. The array 𝚂𝙰1 denotes the inverse suffix array, defined by 𝚂𝙰1[𝚂𝙰[k]]=k.

To decide at iteration i whether the new position itext=n𝚂𝙰[i]+1 should replace or join the existing candidates, we use an auxiliary array 𝚙𝚛𝚎𝚟𝚆[1..σ] that records, for each character, the most recent weighted occurrence (its 𝑖𝑛𝑑𝑒𝑥) together with the corresponding 𝑤𝑒𝑖𝑔ℎ𝑡. In addition, we employ a monotone stack to compute in amortized 𝒪(1) time 𝚋(i), the largest index i<i such that 𝙻𝙲𝙿[i]<𝙻𝙲𝙿[i]. By comparing 𝚠(i) with 𝙻𝙲𝙿[i], 𝚙𝚛𝚎𝚟𝚆[c].𝑖𝑛𝑑𝑒𝑥 with 𝚋(i), and 𝚙𝚛𝚎𝚟𝚆[c].𝑤𝑒𝑖𝑔ℎ𝑡 with 𝚠(i) (where c=𝙱𝚆𝚃[i]), we can determine whether the position itext is a superior candidate to those currently stored. This strategy guarantees that, at any time, we retain only the most relevant candidate for each distinct character.  

The Ejection Mechanism. As the algorithm streams through the arrays, the current 𝙻𝙲𝙿 value, 𝙻𝙲𝙿[i], acts as a threshold. When 𝙻𝙲𝙿[i] drops below the weight of a candidate in 𝚛𝚘𝚠𝙻𝚒𝚜𝚝, this indicates that the corresponding right-maximal extension can no longer be extended. The candidate position is then ejected from the doubly-linked list and appended to the output list, which stores a sublist of the suffixient array. Since 𝚛𝚘𝚠𝙻𝚒𝚜𝚝 is sorted, each ejection takes constant time, resulting in overall linear running time.  

Our algorithm is asymptotically faster than the one-pass algorithm of Cenzato et al. [5]. Its working space is bounded by 𝒪(h+χ), in addition to the 𝚂𝙰, 𝙻𝙲𝙿, and 𝙱𝚆𝚃 arrays, while computing a minimum-size suffixient set requires only 𝒪(h+σ) words of working space. Here, h denotes the height of the suffix tree built over the reversed text, that is, the maximum number of branching nodes along any root-to-leaf path. Since the algorithm processes these arrays in a one-pass streaming fashion, it naturally integrates with prefix-free parsing [3].

2 Preliminaries

Our model of computation is a random access machine (RAM) endowed with comparison operations and basic arithmetic operations, including only addition and subtraction [2].

Let Σ={1,,σ} be the alphabet set. We assume that the text T[1..n] ends at the position n with a special character $ that never appears in T[1..n1]. So, the alphabet size is at least two. Without loss of generality, we further assume that σn.

For any string S, let (S)rev denote its reverse. We define the reversed text Trev[1..n] as a specific construction distinct from (T)rev; while (T)rev simply reverses the entire sequence, Trev preserves the sentinel $ at the final position. Formally, Trev[n]=$ and Trev[i]=T[ni] for 1i<n. Let 𝚂𝙰[1..n], 𝙱𝚆𝚃[1..n], and 𝙻𝙲𝙿[1..n] denote the suffix array, the Burrows–Wheeler transform, and the Longest Common Prefix array of the reversed text Trev, respectively. Specifically, 𝚂𝙰[1..n] is a permutation of the indices {1,,n} such that the suffixes Trev[𝚂𝙰[i]..n] are arranged in lexicographical order for 1in. And 𝙱𝚆𝚃[i]=$ if 𝚂𝙰[i]=1; otherwise, 𝙱𝚆𝚃[i]=Trev[𝚂𝙰[i]1]=T[n𝚂𝙰[i]+1]. Moreover, 𝙻𝙲𝙿[1]=1, and for i>1, 𝙻𝙲𝙿[i] is the length of the longest common prefix of (T[1..n𝚂𝙰[i]])rev and (T[1..n𝚂𝙰[i1]])rev.

Given two strings α and β, we say that α is co-lexicographically smaller than β if and only if one of the following holds: i) there exists an index k such that α[|α|i+1]=β[|β|i+1] for all 1i<k, and α[|α|k+1]<β[|β|k+1]; ii) or α is a proper suffix of β.

Throughout this paper, we reserve the term position for locations in the text T[1..n], and the term index for locations in the arrays 𝚂𝙰, 𝙱𝚆𝚃, and 𝙻𝙲𝙿. For example, we use the symbol jtext to denote a position in T[1..n], and write j (without the subscript “text”) to denote an index in these arrays. Consequently, for 1jn we have jtext=n𝚂𝙰[j]+1, and for 1jtextn we have j=𝚂𝙰1[njtext+1].

Definition 1 (Right-maximal substrings and one-character right-maximal extension [5]).

For a text T[1..n] containing at least two distinct characters, including $, a substring T[itext..jtext] (jtextitext1) is a right-maximal substring if there exist at least two distinct characters a,bΣ such that both T[itext..jtext]a and T[itext..jtext]b are substrings of T. For any right-maximal substring str, we call strc for cΣ a one-character right-maximal extension of str if strc is a substring of T[1..n].

Note that the empty string is always a right-maximal substring, since σ2.

Definition 2 (Suffixient set [5]).

A set 𝒳{1,,n} is suffixient for a text T[1..n] if, for every one-character right-maximal extension T[itext..jtext] (jtextitext) of every right-maximal string T[itext..jtext1], there exists a position xtext𝒳 such that T[itext..jtext] is a suffix of T[1..xtext].

Since T[n]=$ and $ does not appear in T[1..n1], any suffixient set must contain n.

Definition 3 (Suffixient array [5]).

A suffixient array sA of a text T[1..n] is a minimum-size suffixient set for T, whose elements are ordered so that the prefixes T[1..sA[1]],T[1..sA[2]], appear in colexicographic order.

Computing the 𝚠() values.

Let x[1..n] be an index. We say that x is a start-run boundary if x>1 and 𝙱𝚆𝚃[x]𝙱𝚆𝚃[x1], and an end-run boundary if x<n and 𝙱𝚆𝚃[x]𝙱𝚆𝚃[x+1]. Any index that is either a start- or end-run boundary is called a run boundary.

To every index x[1..n] we assign a weight 𝚠(x), defined as the maximum length of a right-maximal substring that is a suffix of T[1..n𝚂𝙰[x]], if x is a run-boundary, and 1, otherwise. Note that if x is a run-boundary, then a right-maximal substring ending at position n𝚂𝙰[x] surely exists, hence 𝚠(x) is well-defined.

The value 𝚠(x) can then be computed as follows: if x is not a run boundary, then we set 𝚠(x):=1, as per the definition. If x is only a start-run (resp., only an end-run) boundary, then 𝚠(x)=𝙻𝙲𝙿[x] (resp., 𝚠(x)=𝙻𝙲𝙿[x+1]). If x is both a start- and end-run boundary, then 𝚠(x)=max{𝙻𝙲𝙿[x],𝙻𝙲𝙿[x+1]}. Each value can be computed in 𝒪(1) time using 𝒪(1) working space. The pseudocode is deferred to Appendix A.

Computing the 𝚋() values.

For any x[1..n], define 𝚋(x) as the largest index 𝚋(x)<x for which 𝙻𝙲𝙿[𝚋(x)]<𝙻𝙲𝙿[x]. If no such index exists, set 𝚋(x):=1. Define 𝚎(x) as the smallest index 𝚎(x)>x such that 𝙻𝙲𝙿[𝚎(x)]<𝙻𝙲𝙿[x]. If no such index exists, set 𝚎(x):=n+1. The value 𝚎(x) is only used conceptually throughout this paper, so we only show an algorithm that computes 𝚋(x).

In our setting, the entries in 𝙻𝙲𝙿[1..n] are enumerated sequentially from 𝙻𝙲𝙿[1],𝙻𝙲𝙿[2], in a stream; immediately after reading the entry 𝙻𝙲𝙿[i] for each i>1, our goal is to compute the index 𝚋(i) in amortized constant time.

Our data structure is a regular stack that stores tuples of form (index,val,b_val), where index[1..n], val=𝙻𝙲𝙿[index], and b_val=𝚋(index). We call this stack monotone stack as the tuples in the stack are always sorted decreasingly by the val entries from top to the bottom. Initially, we create an empty stack S and push the triple (1,1,1) onto S.

When 𝙻𝙲𝙿[i] is available, we first check if or not the tuple at the top of S has index=i. If so, then we return the b_val value of the top tuple; this means that 𝚋(i) has already been computed before (note that in our setting, when 𝙻𝙲𝙿[i] is available, we might call 𝚋(i) more than once). Otherwise, we pop every tuple in the stack with val𝙻𝙲𝙿[i] out of S, until we find the tuple t with t.val<𝙻𝙲𝙿[i]. Then, we return t.index right after we push the tuple (i,𝙻𝙲𝙿[i],t.index) onto the top of S. The pseudocode can be found in Appendix B.

Since the tuple associated with each 𝙻𝙲𝙿[i] is pushed onto S exactly once and popped from S at most once, the overall running time is 𝒪(n). Consequently, 𝚋(i) can be computed in amortized constant time. The correctness is standard and omitted; a similar idea has appeared in [7, Theorem 2] and [1, Section 4] for a related purpose.

Let |S|i denote the size of the stack immediately after processing 𝙻𝙲𝙿[i]. By Proposition 4 (below), max{|S|i1<in} is upper bounded by the height of the suffix tree [16] built over Trev[1..n], plus one. In particular, if T[1..n]=an, then |S|Ω(n) , and if the text is uniform, then |S|𝒪(logn) with high probability [6]. However, as noted in [1, Section 5.1], the stack size in practice is much smaller.

Proposition 4.

During the sequential execution of 𝚋(i) for i=2,3,,n, the size of the monotone stack is bounded by h+1 in the worst case, where h denotes the height of the suffix tree constructed over the reverse of the input text, that is the maximum number of branching nodes on any root–to–leaf path.

Proof.

Let Trie denote the suffix tree constructed over Trev[1..n]. For each i, let Ai denote the list of all index entries, excluding 1, of the triples stored in the monotone stack S (in increasing order) upon completion of the execution of 𝚋(i). Thus, |Ai|=|S|i1.

Consider any j,j′′Ai with j<j′′, and any s(j..j′′]. Following the algorithm, we have 𝙻𝙲𝙿[j]<𝙻𝙲𝙿[s]; otherwise, the triple indexed by j would have been popped from the stack. Consequently, Trev[𝚂𝙰[j]..𝚂𝙰[j]+𝙻𝙲𝙿[j]1] is a prefix of Trev[𝚂𝙰[j′′]..𝚂𝙰[j′′]+𝙻𝙲𝙿[j′′]1].

For each jAi, let uj denote the node in Trie that is the lowest common ancestor of the 𝚂𝙰[j1]-leaf and the 𝚂𝙰[j]-leaf. Observe that uj is the locus of the string Trev[𝚂𝙰[j]..𝚂𝙰[j]+𝙻𝙲𝙿[j]1]. Hence, uj is an ancestor of uj′′, and the nodes {ujjAi} all lie on a single root-to-leaf path in Trie.

It follows that |Ai|h. Since |Ai|=|S|i1, we conclude that |S|ih+1.

3 A Smallest Suffixient Set: A New Characterization and Proof

In this section, we characterize a minimum-size suffixient set based the concepts of 𝚠(),𝚋(), and 𝚎() and prove its correctness.

Definition 5 (𝙲𝚊𝚗𝚍𝚝).

Let 1a<an and cΣ. Define 𝙲𝚊𝚗𝚍𝚝c(a,a) as the smallest index p[a..a) with 𝙱𝚆𝚃[p]=c and 𝚠(p)=max{𝚠(x)ax<a,𝙱𝚆𝚃[x]=c,𝚠(x)0}; if no such index exists, set 𝙲𝚊𝚗𝚍𝚝c(a,a):=1.

By Definition 5, for any index x with 𝙱𝚆𝚃[x]𝙱𝚆𝚃[x1] and any c{𝙱𝚆𝚃[x],𝙱𝚆𝚃[x1]}, it follows that 𝙲𝚊𝚗𝚍𝚝c(𝚋(x),𝚎(x))1. Indeed, both x and x1 lie within the interval [𝚋(x),𝚎(x)), and 𝚠(x1)0 as well as 𝚠(x)0. Observation 6 will be used throughout the remainder of the paper.

Observation 6.

For any index 1<xn such that 𝙱𝚆𝚃[x]𝙱𝚆𝚃[x1], and any c{𝙱𝚆𝚃[x],𝙱𝚆𝚃[x1]}, we have 𝚠(x,c)𝙻𝙲𝙿[x], where x,c=𝙲𝚊𝚗𝚍𝚝c(𝚋(x),𝚎(x)).

Proof.

Since 𝙱𝚆𝚃[x]𝙱𝚆𝚃[x1] and c{𝙱𝚆𝚃[x],𝙱𝚆𝚃[x1]}, Definition 5 implies that x,c=𝙲𝚊𝚗𝚍𝚝c(𝚋(x),𝚎(x))>1 and that 𝚠(x,c)=max{𝚠(j)𝚋(x)j<𝚎(x),𝙱𝚆𝚃[j]=c}.

Let {x,x1} be the index with 𝙱𝚆𝚃[]=c. By the definition of 𝚠(), it follows that 𝚠()𝙻𝙲𝙿[x]. Moreover, since [𝚋(x),𝚎(x)) by the definitions of 𝚋() and 𝚎(), and 𝙱𝚆𝚃[]=c, the maximality of 𝚠(x,c) implies 𝚠(x,c)𝚠()𝙻𝙲𝙿[x].

Our goal is to show that 𝙵𝚞𝚕𝚕𝙻, defined in Definition 7, is a minimum-size suffixient set, thereby providing a new characterization of such sets in terms of 𝚠(), 𝚋(), and 𝚎(). To this end, one possible approach is to establish a bijection between 𝙵𝚞𝚕𝚕𝙻 and the set of all super-maximal extensions, as defined in [5, Definition 32]. This approach implies, as demonstrated in [5, Section 5], that 𝙵𝚞𝚕𝚕𝙻 is a minimum-size suffixient set. Instead, we present a new proof that does not rely on the notion of super-maximal extensions.

Definition 7 (𝙵𝚞𝚕𝚕𝙻).

Define 𝙵𝚞𝚕𝚕𝙻={n𝚂𝙰[p]+1p=𝙲𝚊𝚗𝚍𝚝c(𝚋(x),𝚎(x)),1<xn,𝙱𝚆𝚃[x]𝙱𝚆𝚃[x1],c{𝙱𝚆𝚃[x],𝙱𝚆𝚃[x1]}}.

We first prove that 𝙵𝚞𝚕𝚕𝙻 is suffixient for the text T[1..n], applying Definition 2.

Lemma 8.

The set 𝙵𝚞𝚕𝚕𝙻 is suffixient for the text T[1..n].

Proof.

By Definition 2, it suffices to show the following: for any right-maximal substring str of T[1..n] and for any one-character right-maximal extension strc of str, there exists a position xtext𝙵𝚞𝚕𝚕𝙻 such that strc is a suffix of T[1..xtext].

Since str is right-maximal in T[1..n], there exists another character ac such that stra also occurs as a substring of T[1..n]. Consequently, there exists at least one interval [,z] such that both characters a and c occur in 𝙱𝚆𝚃[..z], and the prefix T[1..n𝚂𝙰[i]] has str as a suffix for every index i[,z].

Let [,z] be the smallest such interval. Without loss of generality, assume that 𝙱𝚆𝚃[]c and 𝙱𝚆𝚃[z]=c; the case 𝙱𝚆𝚃[]=c and 𝙱𝚆𝚃[z]c is symmetric. By the minimality of the interval [,z], we have 𝙱𝚆𝚃[z]𝙱𝚆𝚃[z1]. Let q:=𝙲𝚊𝚗𝚍𝚝c(𝚋(z),𝚎(z)). Since 𝙱𝚆𝚃[z]=c=𝙱𝚆𝚃[q] and 𝙱𝚆𝚃[z]𝙱𝚆𝚃[z1], it follows that 𝚠(q)𝚠(z)𝙻𝙲𝙿[z]|str|; moreover, as T[n𝚂𝙰[q]+1]=𝙱𝚆𝚃[q]=c, we have strc is a suffix of T[1..n𝚂𝙰[q]+1]. Setting xtext:=n𝚂𝙰[q]+1, we conclude that xtext𝙵𝚞𝚕𝚕𝙻, completing the proof.

Lemma 9.

Define x,c:=𝙲𝚊𝚗𝚍𝚝c(𝚋(x),𝚎(x)) and x,c:=𝙲𝚊𝚗𝚍𝚝c(𝚋(x),𝚎(x)) for any two start-run boundaries x and x, and for any c{𝙱𝚆𝚃[x],𝙱𝚆𝚃[x1]} and c{𝙱𝚆𝚃[x],𝙱𝚆𝚃[x1]}, respectively. If x,cx,c and 𝚠(x,c)𝚠(x,c), then T[n𝚂𝙰[x,c]+1𝚠(x,c)..n𝚂𝙰[x,c]+1] cannot be a suffix of T[n𝚂𝙰[x,c]+1𝚠(x,c)..n𝚂𝙰[x,c]+1].

Proof.

By Definition 5, we have x,c>1, 𝚠(x,c)>1, x,c>1, and 𝚠(x,c)>1.

Any index with 𝚠()0 induces the right-maximal substring T[n𝚂𝙰[]+1𝚠(),n𝚂𝙰[]], which cannot contain the symbol $. Recall that the suffix array 𝚂𝙰 is constructed over Trev[1..n]. Therefore, the reverse of a right-maximal substring induces an interval in the suffix array, called its SA-interval [11, Section 2.2.2]. Formally, for any index with 𝚠()0, let [s(),t()] denote the SA-interval of the string T[n𝚂𝙰[]+1𝚠()..n𝚂𝙰[]], where s() is the minimum index i[1..n] such that the string is a suffix of T[1..n𝚂𝙰[i]], and t() is defined analogously as the maximum such index.

Claim 10.

We have [s(x,c),t(x,c)][𝚋(x),𝚎(x)) and [s(x,c),t(x,c)][𝚋(x),𝚎(x)).

Proof.

It suffices to show that s(x,c)𝚋(x) and t(x,c)<𝚎(x). By the definition of SA-intervals, s(x,c)=max{i[1..x,c)𝙻𝙲𝙿[i]<𝚠(x,c)} and t(x,c)=min{i[x,c..n]𝙻𝙲𝙿[i]<𝚠(x,c)}1.

Assume for contradiction that s(x,c)<𝚋(x). Since 𝚋(x)x,c, this implies 𝙻𝙲𝙿[𝚋(x)]𝚠(x,c); otherwise, s(x,c)𝚋(x) by definition. By the definition of 𝚋(), 𝙻𝙲𝙿[𝚋(x)]<𝙻𝙲𝙿[x], yielding 𝙻𝙲𝙿[x]>𝚠(x,c), which contradicts Observation 6.

A symmetric argument shows that t(x,c)<𝚎(x). If 𝚎(x)t(x,c), then since x,c<𝚎(x) we obtain 𝙻𝙲𝙿[𝚎(x)]𝚠(x,c), which together with 𝙻𝙲𝙿[x]>𝙻𝙲𝙿[𝚎(x)] again contradicts Observation 6. This proves the claim.

We now prove the lemma by contradiction. Assume that T[n𝚂𝙰[x,c]+1𝚠(x,c)..n𝚂𝙰[x,c]+1] is a suffix of T[n𝚂𝙰[x,c]+1𝚠(x,c)..n𝚂𝙰[x,c]+1], which implies c=c. Consequently, T[n𝚂𝙰[x,c]+1𝚠(x,c)..n𝚂𝙰[x,c]] is a suffix of T[n𝚂𝙰[x,c]+1𝚠(x,c)..n𝚂𝙰[x,c]], and hence x,c[s(x,c),t(x,c)].

If 𝚠(x,c)<𝚠(x,c), then by Claim 10 we have x,c[𝚋(x),𝚎(x)), which implies 𝚠(𝙲𝚊𝚗𝚍𝚝c(𝚋(x),𝚎(x)))>𝚠(x,c), contradicting the definition of x,c. If instead 𝚠(x,c)=𝚠(x,c), then the two substrings are equal, so s(x,c)=s(x,c) and t(x,c)=t(x,c). Assuming without loss of generality that x,c<x,c, we obtain 𝚋(x)s(x,c)x,c<x,ct(x,c)<𝚎(x), which contradicts the choice of x,c as 𝙲𝚊𝚗𝚍𝚝c(𝚋(x),𝚎(x)). In both cases, we reach a contradiction, completing the proof.

Finally, we show that 𝙵𝚞𝚕𝚕𝙻 has minimum possible size, applying the pigeonhole principle.

Lemma 11.

The list 𝙵𝚞𝚕𝚕𝙻 is a minimum-size suffixient set.

Proof.

Let F be an arbitrary suffixient set. Our goal is to show that |F||𝙵𝚞𝚕𝚕𝙻|.

Suppose, for the sake of contradiction, that |F|<|𝙵𝚞𝚕𝚕𝙻|. Recall that each xtext𝙵𝚞𝚕𝚕𝙻 corresponds to a right-maximal extension T[xtext𝚠(𝚂𝙰1[nxtext+1])..xtext]. By the definition of suffixient sets, the string T[xtext𝚠(𝚂𝙰1[nxtext+1])..xtext] must be a suffix of T[1..ftext] for some ftextF.

Since |F|<|𝙵𝚞𝚕𝚕𝙻|, the pigeonhole principle implies that there exist two distinct indices xtext,ytext𝙵𝚞𝚕𝚕𝙻 such that both T[xtext𝚠(𝚂𝙰1[nxtext+1])..xtext] and T[ytext𝚠(𝚂𝙰1[nytext+1])..ytext] are suffixes of the same prefix T[1..ftext] for some ftextF. Consequently, one of these two strings must be a suffix of the other.

However, by Lemma 9, no such pair xtext and ytext can exist. This contradiction shows that our assumption was false, and therefore |F||𝙵𝚞𝚕𝚕𝙻|.

Lemmas 11 and 8 together imply that 𝙵𝚞𝚕𝚕𝙻 is a minimum-size suffixient set.

4 Computing 𝒔𝑨: A One-Pass Algorithm with One-Step Look-ahead

In this section, we present a new one-pass algorithm that computes suffixient arrays. The algorithm iterates over the index i from 1 to n and makes decisions upon reading the triple (𝙱𝚆𝚃[i],𝚂𝙰[i],𝙻𝙲𝙿[i]). During the iteration at index i, the algorithm may additionally access the input of the next iteration, namely the triple (𝙱𝚆𝚃[i+1],𝚂𝙰[i+1],𝙻𝙲𝙿[i+1]), but it requires no information about inputs beyond i+1. For this reason, we refer to it as a one-pass algorithm with one-step look-ahead.

An overview of this section is as follows. Section 4.1 introduces the data structures and the three invariants maintained by the algorithm. Section 4.2 then presents a detailed description of the algorithm together with the underlying intuitions. Since the algorithm invokes the operation 𝙲𝚊𝚗𝚍𝚝 at each iteration, it may initially appear to require quadratic time. Before addressing the complexity analysis, we establish the correctness of the algorithm in Section 4.3. Next, Section 4.4 shows that a validity test for 𝙲𝚊𝚗𝚍𝚝, such as checking whether i=𝙲𝚊𝚗𝚍𝚝(,), can be implemented using a constant number of amortized 𝒪(1)-time operations, thereby justifying that the algorithm runs in a one-pass fashion. Finally, Section 4.5 presents the overall complexity analysis.

4.1 The Data Structures

During the execution of the algorithm, we maintain the following data structures:

  • A monotone stack used to compute 𝚋(i) during the i-th iteration of the algorithm;

  • A doubly-linked list, 𝚛𝚘𝚠𝙻𝚒𝚜𝚝, containing at most σ triples drawn from [1..n]×Σ×[0..n];

  • An array 𝙼𝙰𝙿[1..σ] such that, for each cΣ, the entry 𝙼𝙰𝙿[c] stores a pointer to the (unique) triple (,c,) in 𝚛𝚘𝚠𝙻𝚒𝚜𝚝, if such a triple exists, and stores null otherwise;

  • For every cΣ, a linked list 𝚛𝚎𝚜𝚞𝚕𝚝c containing positions drawn from [1..n].

For convenience, we define 𝚛𝚎𝚜𝚞𝚕𝚝 as the list obtained by concatenating all 𝚛𝚎𝚜𝚞𝚕𝚝1,, 𝚛𝚎𝚜𝚞𝚕𝚝σ in this order.

For every triple (ptext,,) stored in 𝚛𝚘𝚠𝙻𝚒𝚜𝚝, we will refer to ptext as a candidate position, and to the corresponding index 𝚂𝙰1[nptext+1] as a candidate index.

The following invariants describe the content of the above data-structures during the i-th iteration of the algorithm. Among these, Invariant 2 characterizes the candidate indices stored in 𝚛𝚘𝚠𝙻𝚒𝚜𝚝.

  • Invariant 1: For each cΣ, 𝚛𝚎𝚜𝚞𝚕𝚝c is the sub-list of 𝙵𝚞𝚕𝚕𝙻 containing exactly those positions jtext such that 𝚂𝙰1[njtext+1]<i and T[jtext]=c.

  • Invariant 2: The list 𝚛𝚘𝚠𝙻𝚒𝚜𝚝 contains exactly those triples (n𝚂𝙰[j]+1,𝙱𝚆𝚃[j],𝚠(j)) where the index j satisfies either of the following conditions:

    • 𝚠(j)=𝙻𝙲𝙿[j], ji1<𝚎(j), and 𝙲𝚊𝚗𝚍𝚝𝙱𝚆𝚃[j](𝚋(j),i)=j; or

    • 𝚠(j)>1, 𝚠(j)𝙻𝙲𝙿[j], ji1<𝚎(j+1), and 𝙲𝚊𝚗𝚍𝚝𝙱𝚆𝚃[j](𝚋(j+1),i)=j.

  • Invariant 3: The triples in 𝚛𝚘𝚠𝙻𝚒𝚜𝚝 are sorted in non-increasing order by weight.

4.2 The Algorithm

In this section we give an high-level description of the algorithm. As already mentioned above, the algorithm iterates over indexes i=1,2,,n once. In Step 0 we describe the special case of i=1. Then, Step 1 and Step 2 are executed in every successive iteration i=2,3,n. Finally, a last step will return the list 𝙵𝚞𝚕𝚕𝙻 using the information computed in the preceding iterations.

For both Step 1 and 2 we give some intuitive explanation on why they are correct. The full proof is given in Section 4.3. The pseudocode of the algorithm is deferred to the Appendix C.

Step 0.

In the first iteration, if 𝙱𝚆𝚃[2]𝙱𝚆𝚃[1] we append the triple (n𝚂𝙰[1]+1,𝙱𝚆𝚃[1],𝚠(1)) to the front of 𝚛𝚘𝚠𝙻𝚒𝚜𝚝, and we set 𝙼𝙰𝙿[𝙱𝚆𝚃[1]] to point to such triple; otherwise, no triple is inserted in 𝚛𝚘𝚠𝙻𝚒𝚜𝚝. Every list 𝚛𝚎𝚜𝚞𝚕𝚝c remains empty.

We now consider iteration i for i(1,n]. The operations are divided into two steps.

Step 1.

Remove from 𝚛𝚘𝚠𝙻𝚒𝚜𝚝 every triple (ptext,c,w) such that w>𝙻𝙲𝙿[i]. Set 𝙼𝙰𝙿[c] to null and append the position ptext to the tail of the list 𝚛𝚎𝚜𝚞𝚕𝚝c.

Intuition behind Step 1.

We provide some intuition for why the position ptext appended to 𝚛𝚎𝚜𝚞𝚕𝚝char always belongs to 𝙵𝚞𝚕𝚕𝙻, a property required to maintain Invariant 1.

Let j=𝚂𝙰1[nptext+1]. By Lemma 15, depending on whether 𝙻𝙲𝙿[j]=w, we have either j=𝙲𝚊𝚗𝚍𝚝c(𝚋(j),i) or j=𝙲𝚊𝚗𝚍𝚝c(𝚋(j+1),i). Moreover, Lemma 16 shows that when 𝙻𝙲𝙿[i]<w, the corresponding interval endpoint satisfies 𝚎(j)=i in the former case and 𝚎(j+1)=i in the latter. Thus, intuitively, j is always selected as a candidate index over one of the intervals [𝚋(j),𝚎(j)) or [𝚋(j+1),𝚎(j+1)).

Furthermore, Lemma 17 guarantees that in the first case 𝙱𝚆𝚃[j]𝙱𝚆𝚃[j1], while in the second case 𝙱𝚆𝚃[j+1]𝙱𝚆𝚃[j]. In both situations, j satisfies the defining conditions of 𝙵𝚞𝚕𝚕𝙻. Therefore, the position ptext appended to 𝚛𝚎𝚜𝚞𝚕𝚝c indeed belongs to 𝙵𝚞𝚕𝚕𝙻.

Lemma 13 states that the triples in 𝚛𝚘𝚠𝙻𝚒𝚜𝚝 are sorted in non-increasing order of their weight values. As a result, every triple satisfying 𝙻𝙲𝙿[i]<w can be identified and removed in 𝒪(1) time.

Step 2.

Compute the value 𝚠(i). If 𝚠(i)=1, proceed immediately to the next iteration. Otherwise, compute the index 𝚋(i) and initialize a variable 𝚌𝚞𝚛𝚛_𝚋 to 𝚋(i). If 𝚠(i)𝙻𝙲𝙿[i], update 𝚌𝚞𝚛𝚛_𝚋 to 𝚋(i+1).

Assume that c=𝙱𝚆𝚃[i]. We then check whether i=𝙲𝚊𝚗𝚍𝚝c(𝚌𝚞𝚛𝚛_𝚋,i+1). If this condition does not hold, proceed immediately to the next iteration. If 𝙼𝙰𝙿[c]null, remove from 𝚛𝚘𝚠𝙻𝚒𝚜𝚝 the triple pointed to by 𝙼𝙰𝙿[c]. Then prepend the triple (n𝚂𝙰[i]+1,c,𝚠(i)) to 𝚛𝚘𝚠𝙻𝚒𝚜𝚝 and update 𝙼𝙰𝙿[c] to point to the new head.

Intuition behind Step 2.

We now give an informal explanation of why both Invariant 2 and Invariant 3 are maintained after this step. If either 𝚠(i)=1 or i𝙲𝚊𝚗𝚍𝚝c(𝚌𝚞𝚛𝚛_𝚋,i+1), the algorithm immediately proceeds to the next iteration. In this situation, no triple is added to 𝚛𝚘𝚠𝙻𝚒𝚜𝚝, and both invariants are trivially maintained. Otherwise, the triple (n𝚂𝙰[i]+1,𝙱𝚆𝚃[i],𝚠(i)) is added to the front of 𝚛𝚘𝚠𝙻𝚒𝚜𝚝. By Lemma 13, this insertion preserves the non-increasing order of weights in 𝚛𝚘𝚠𝙻𝚒𝚜𝚝, ensuring that Invariant 3 holds.

It remains to argue that the newly added triple satisfies one of the conditions in Invariant 2. Two cases arise, depending on whether 𝙻𝙲𝙿[i]=𝚠(i). If 𝙻𝙲𝙿[i]=𝚠(i), then 𝚌𝚞𝚛𝚛_𝚋=𝚋(i) and i=𝙲𝚊𝚗𝚍𝚝c(𝚋(i),i+1); otherwise, 𝙻𝙲𝙿[i]𝚠(i), 𝚌𝚞𝚛𝚛_𝚋=𝚋(i+1), and i=𝙲𝚊𝚗𝚍𝚝c(𝚋(i+1),i+1). Setting j:=i, we have 𝚠(j)>1, and either ji+11<𝚎(j) with 𝙲𝚊𝚗𝚍𝚝c(𝚋(j),i+1)=j, or ji+11<𝚎(j+1) with 𝙲𝚊𝚗𝚍𝚝c(𝚋(j+1),i+1)=j. In both cases, the conditions of Invariant 2 are satisfied. In Section 4.4, we show how the test i=𝙲𝚊𝚗𝚍𝚝c(𝚌𝚞𝚛𝚛_𝚋,i+1) can be implemented in amortized constant time.

Final step after the 𝒏 iterations.

If 𝚛𝚘𝚠𝙻𝚒𝚜𝚝 is nonempty, append all positions ptext from the remaining tuples (ptext,c,w) to their respective 𝚛𝚎𝚜𝚞𝚕𝚝c lists. Then all the 𝚛𝚎𝚜𝚞𝚕𝚝c lists are concatenated to obtain 𝚛𝚎𝚜𝚞𝚕𝚝, which is then returned as the suffixient array.

In the next section we prove that 𝚛𝚎𝚜𝚞𝚕𝚝=𝙵𝚞𝚕𝚕𝙻. Hence, by Lemma 11, the output list is suffixient and of minimum size. Note that the prefix list {T[1..𝚂𝙰[1]],,T[1..𝚂𝙰[n]]} is sorted in co-lexicographic order. We further show that this ordering ensures that the indices in the output list are correctly sorted, in accordance with the definition of a suffixient array.

4.3 Correctness of the Algorithm

In this section, we prove the correctness of the algorithm. The proof is divided into three parts. In Section 4.3.1 we prove that the triples in 𝚛𝚘𝚠𝙻𝚒𝚜𝚝 are always sorted non-increasingly by their weight values. Then, in Section 4.3.2, we show that for any character cΣ and any position text𝚛𝚎𝚜𝚞𝚕𝚝c, it holds that text𝙵𝚞𝚕𝚕𝙻. Finally, in Section 4.3.3, we prove the converse: every text𝙵𝚞𝚕𝚕𝙻 is added to 𝚛𝚎𝚜𝚞𝚕𝚝c for some cΣ; we also show that the order of indices in the final list 𝚛𝚎𝚜𝚞𝚕𝚝 is consistent with the order induced by the suffixient array.

4.3.1 The Ordering on the Triples in 𝚛𝚘𝚠𝙻𝚒𝚜𝚝

Lemma 12.

Let c=𝙱𝚆𝚃[i]. If i=𝙲𝚊𝚗𝚍𝚝c(𝚌𝚞𝚛𝚛_𝚋,i+1) at iteration i and 𝚠(i)𝙻𝙲𝙿[i], then one of the following holds: either 𝙱𝚆𝚃[j]=c for every j[𝚌𝚞𝚛𝚛_𝚋,i), or 𝙱𝚆𝚃[j]c for every j[𝚌𝚞𝚛𝚛_𝚋,i).

Proof.

Suppose, for the sake of contradiction, that there exists an index j[𝚌𝚞𝚛𝚛_𝚋,i1) such that either 𝙱𝚆𝚃[j]=c and 𝙱𝚆𝚃[j+1]c, or 𝙱𝚆𝚃[j]c and 𝙱𝚆𝚃[j+1]=c. Let j′′{j,j+1} be such that 𝙱𝚆𝚃[j′′]=c.

We claim that 𝚠(j′′)𝙻𝙲𝙿[j+1]. Indeed, if 𝙱𝚆𝚃[j]=c and 𝙱𝚆𝚃[j+1]c, then by the definition of 𝚠() we have 𝚠(j′′)=𝚠(j)𝙻𝙲𝙿[j+1]. Otherwise, if 𝙱𝚆𝚃[j]c and 𝙱𝚆𝚃[j+1]=c, then 𝚠(j′′)=𝚠(j+1)𝙻𝙲𝙿[j+1].

Define :=i if 𝚠(i)=𝙻𝙲𝙿[i], and otherwise :=i+1. By construction, 𝚋()=𝚌𝚞𝚛𝚛_𝚋 and 𝚠(i)=𝙻𝙲𝙿[]. Since j+1(𝚌𝚞𝚛𝚛_𝚋,i) and (𝚌𝚞𝚛𝚛_𝚋,i)(𝚋(),), the definition of 𝚋() implies 𝙻𝙲𝙿[j+1]𝙻𝙲𝙿[]. Consequently, 𝚠(j′′)𝙻𝙲𝙿[j+1]𝙻𝙲𝙿[]=𝚠(i).

Finally, since 𝙱𝚆𝚃[j′′]=c, 𝚌𝚞𝚛𝚛_𝚋=𝚋()j′′<i, and 𝚠(j′′)𝚠(i), this contradicts i=𝙲𝚊𝚗𝚍𝚝c(𝚌𝚞𝚛𝚛_𝚋,i+1). Hence, the assumption is false, and the lemma follows.

Note that in Lemma 12, if 𝚠(i)>𝙻𝙲𝙿[i], then 𝚠(i)=𝙻𝙲𝙿[i+1]>𝙻𝙲𝙿[i] and 𝚌𝚞𝚛𝚛_𝚋=𝚋(i+1)=i; hence, the interval [𝚌𝚞𝚛𝚛_𝚋,i) is empty.

Lemma 13.

The entries in 𝚛𝚘𝚠𝙻𝚒𝚜𝚝 are sorted in non-increasing order by weight.

Proof.

We prove the claim by induction on the iteration index.

In the first iteration, if 𝙱𝚆𝚃[1]=𝙱𝚆𝚃[2], no triple is added to 𝚛𝚘𝚠𝙻𝚒𝚜𝚝, so it remains empty. Otherwise, 𝚛𝚘𝚠𝙻𝚒𝚜𝚝 contains exactly one triple. In both cases, the claim holds trivially.

Assume as induction hypothesis that after each of the first (i1) iterations (for some i>1), the triples in 𝚛𝚘𝚠𝙻𝚒𝚜𝚝 are sorted in non-increasing order of their 𝚠 values.

Now consider the i-th iteration. Any triple removed from 𝚛𝚘𝚠𝙻𝚒𝚜𝚝 in Step 1 is always removed from the front and has weight strictly greater than 𝙻𝙲𝙿[i]. Therefore, after Step 1, the relative order of the remaining triples is preserved. Let (ptext,c,w) denote the triple currently at the front of 𝚛𝚘𝚠𝙻𝚒𝚜𝚝 (if any); then we have 𝙻𝙲𝙿[i]w.

If no triple is added to 𝚛𝚘𝚠𝙻𝚒𝚜𝚝 in Step 2, the claim follows immediately from the induction hypothesis. Otherwise, we must show that the newly added triple (n𝚂𝙰[i]+1,𝙱𝚆𝚃[i],𝚠(i)) satisfies 𝚠(i)w. Since 𝙻𝙲𝙿[i]w, if 𝚠(i)𝙻𝙲𝙿[i], then 𝚠(i)w holds immediately.

It remains to consider the case 𝚠(i)<𝙻𝙲𝙿[i]. Since the triple is added at iteration i, we have i=𝙲𝚊𝚗𝚍𝚝c(𝚌𝚞𝚛𝚛_𝚋,i+1) for c=𝙱𝚆𝚃[i]. Because both i=𝙲𝚊𝚗𝚍𝚝c(𝚌𝚞𝚛𝚛_𝚋,i+1) and 𝚠(i)<𝙻𝙲𝙿[i], Lemma 12 applies, so either 𝙱𝚆𝚃[j]=c for every j[𝚌𝚞𝚛𝚛_𝚋,i) or 𝙱𝚆𝚃[j]c for every such j. Since i>1 and 𝚠(i)<𝙻𝙲𝙿[i], by definition of 𝚠() we must have 𝙱𝚆𝚃[i1]=𝙱𝚆𝚃[i], which implies 𝙱𝚆𝚃[j]=c for every j[𝚌𝚞𝚛𝚛_𝚋,i). Consequently, 𝚠(j)=1 for every j(𝚌𝚞𝚛𝚛_𝚋,i), and either 𝚠(𝚌𝚞𝚛𝚛_𝚋)=𝙻𝙲𝙿[𝚌𝚞𝚛𝚛_𝚋] or 𝚠(𝚌𝚞𝚛𝚛_𝚋)=1. The former case implies 𝚂𝙰1[nptext+1]𝚌𝚞𝚛𝚛_𝚋. Moreover, since 𝚂𝙰1[nptext+1]𝚌𝚞𝚛𝚛_𝚋 and 𝚠(𝚌𝚞𝚛𝚛_𝚋)𝙻𝙲𝙿[𝚌𝚞𝚛𝚛_𝚋], it follows that w𝙻𝙲𝙿[𝚌𝚞𝚛𝚛_𝚋]. On the other hand, since 𝚠(i)<𝙻𝙲𝙿[i], we have 𝚠(i)=𝙻𝙲𝙿[i+1] and 𝚌𝚞𝚛𝚛_𝚋=𝚋(i+1). Because 𝙻𝙲𝙿[i+1]>𝙻𝙲𝙿[𝚌𝚞𝚛𝚛_𝚋] and w𝙻𝙲𝙿[𝚌𝚞𝚛𝚛_𝚋], it follows that 𝚠(i)>w.

Therefore, whether 𝚠(i)𝙻𝙲𝙿[i] or not, we have 𝚠(i)w (in fact, strictly greater in the latter case), so inserting the new triple preserves the non-increasing order in 𝚛𝚘𝚠𝙻𝚒𝚜𝚝. This completes the induction and the proof.

4.3.2 Necessity: 𝚛𝚎𝚜𝚞𝚕𝚝𝙵𝚞𝚕𝚕𝙻

Throughout this section, let 𝚛𝚘𝚠𝙻𝚒𝚜𝚝i denote the state of 𝚛𝚘𝚠𝙻𝚒𝚜𝚝 at the start of the i-th iteration of the algorithm. Let 𝙿𝙾𝚂i={𝚂𝙰1[nptext+1](ptext,,)𝚛𝚘𝚠𝙻𝚒𝚜𝚝i}. For each j𝙿𝙾𝚂i, define xj{j,j+1} by xj=j if 𝚠(j)=𝙻𝙲𝙿[j], and xj=j+1 otherwise. Observation 14 details the properties of xj.

Observation 14.

For each j𝙿𝙾𝚂i, the following hold: (a) either 𝙱𝚆𝚃[j]𝙱𝚆𝚃[j1] or 𝙱𝚆𝚃[j]𝙱𝚆𝚃[j+1]; (b) 𝚠(j)=𝙻𝙲𝙿[xj]; and (c) 𝚋(xj)=𝚌𝚞𝚛𝚛_𝚋 at iteration j.

Proof.

Since j𝙿𝙾𝚂i, the algorithm guarantees 𝚠(j)0. By definition of 𝚠(), this implies 𝚠(j){𝙻𝙲𝙿[j],𝙻𝙲𝙿[j+1]} and that either 𝙱𝚆𝚃[j]𝙱𝚆𝚃[j1] or 𝙱𝚆𝚃[j]𝙱𝚆𝚃[j+1], proving (a), and 𝚠(j)=𝙻𝙲𝙿[xj], proving (b).

If 𝚠(j)𝙻𝙲𝙿[j], then xj=j+1 and 𝚌𝚞𝚛𝚛_𝚋=𝚋(j+1), so 𝚌𝚞𝚛𝚛_𝚋=𝚋(xj) at iteration j. Otherwise, xj=j and 𝚌𝚞𝚛𝚛_𝚋=𝚋(j), so again 𝚌𝚞𝚛𝚛_𝚋=𝚋(xj). This proves (c).

Lemma 15.

For every j𝙿𝙾𝚂i, if 𝚠(j)𝙻𝙲𝙿[j], then 𝙲𝚊𝚗𝚍𝚝c(𝚋(j+1),i)=j, where c=𝙱𝚆𝚃[j]; otherwise, 𝙲𝚊𝚗𝚍𝚝c(𝚋(j),i)=j.

Proof.

Consider the j-th iteration of the algorithm. Since j𝙿𝙾𝚂i and j<i, the tuple (n𝚂𝙰[j]+1,𝙱𝚆𝚃[j],𝚠(j)) is added to 𝚛𝚘𝚠𝙻𝚒𝚜𝚝 in the j-th iteration. As shown in the algorithm, we know that at iteration j, the condition j=𝙲𝚊𝚗𝚍𝚝c(𝚌𝚞𝚛𝚛_𝚋,j+1) holds; moreover, by Observation 14, we have 𝚌𝚞𝚛𝚛_𝚋=𝚋(xj), so, j=𝙲𝚊𝚗𝚍𝚝c(𝚋(xj),j+1).

Since j𝙿𝙾𝚂i, we know that for any index j<j<i, 𝙻𝙲𝙿[j] cannot be smaller than 𝚠(j) (otherwise, the tuple with weight=𝚠(j) would be removed from 𝚛𝚘𝚠𝙻𝚒𝚜𝚝 before iteration i); moreover, if 𝙱𝚆𝚃[j]=c, then 𝚠(j)𝚠(j). Therefore, we have j=𝙲𝚊𝚗𝚍𝚝c(𝚋(xj),i). If 𝚠(j)𝙻𝙲𝙿[j], then xj=j+1, so 𝙲𝚊𝚗𝚍𝚝c(𝚋(j+1),i)=j; otherwise, xj=j, and 𝙲𝚊𝚗𝚍𝚝c(𝚋(j),i)=j, completing the proof.

Lemma 16.

For every j𝙿𝙾𝚂i with 𝙻𝙲𝙿[i]<𝚠(j), if 𝚠(j)𝙻𝙲𝙿[j], then 𝚎(j+1)=i; otherwise, 𝚎(j)=i.

Proof.

Since j𝙿𝙾𝚂i, we know that 𝙻𝙲𝙿[j]𝚠(j) for any index j<j<i (otherwise, j𝙿𝙾𝚂i, a contradiction). Recall that 𝚠(j)=𝙻𝙲𝙿[xj], where xj{j,j+1}, by Observation 14. Since 𝚠(j)>𝙻𝙲𝙿[i] and 𝚠(j)=𝙻𝙲𝙿[xj], we have 𝙻𝙲𝙿[xj]>𝙻𝙲𝙿[i], so i>xj is the smallest index with 𝙻𝙲𝙿[i]<𝙻𝙲𝙿[xj], so, we have 𝚎(xj)=i. If 𝚠(j)𝙻𝙲𝙿[j], then 𝚠(j)=𝙻𝙲𝙿[j+1] and xj=j+1, so 𝚎(j+1)=i; otherwise, 𝚠(j)=𝙻𝙲𝙿[j] and xj=j, so 𝚎(j)=i.

Lemma 17 and Definition 7 imply text𝙵𝚞𝚕𝚕𝙻 for any text𝚛𝚎𝚜𝚞𝚕𝚝c and any c[1,σ].

Lemma 17.

Let (ptext,c,weight) be any triple removed from 𝚛𝚘𝚠𝙻𝚒𝚜𝚝i in the first step of the i-th iteration of the algorithm, and let j=𝚂𝙰1[nptext+1]. Either 𝙲𝚊𝚗𝚍𝚝c(𝚋(j+1),𝚎(j+1))=j and 𝙱𝚆𝚃[j+1]𝙱𝚆𝚃[j], or 𝙲𝚊𝚗𝚍𝚝c(𝚋(j),𝚎(j))=j and 𝙱𝚆𝚃[j]𝙱𝚆𝚃[j1].

Proof.

Since ptext is removed from 𝚛𝚘𝚠𝙻𝚒𝚜𝚝i, we have 𝚠(j)0. Thus, either 𝚠(j)=𝙻𝙲𝙿[j] or 𝚠(j)=𝙻𝙲𝙿[j+1]𝙻𝙲𝙿[j].

If 𝚠(j)𝙻𝙲𝙿[j], then this implies 𝙱𝚆𝚃[j+1]𝙱𝚆𝚃[j], by the definition of 𝚠(). By Lemmas 15 and 16, we obtain 𝙲𝚊𝚗𝚍𝚝c(𝚋(j+1),𝚎(j+1))=j; the statement holds.

Otherwise, 𝚠(j)=𝙻𝙲𝙿[j], this implies that either 𝙱𝚆𝚃[j]𝙱𝚆𝚃[j1], or 𝙱𝚆𝚃[j1]=𝙱𝚆𝚃[j]𝙱𝚆𝚃[j+1] and 𝙻𝙲𝙿[j+1]=𝙻𝙲𝙿[j]. In both cases, Lemmas 15 and 16 imply 𝙲𝚊𝚗𝚍𝚝c(𝚋(j),𝚎(j))=j. Since 𝙲𝚊𝚗𝚍𝚝c(𝚋(j),𝚎(j))=j, if 𝙱𝚆𝚃[j]𝙱𝚆𝚃[j1], then the statement holds immediately. Otherwise, since 𝙻𝙲𝙿[j+1]=𝙻𝙲𝙿[j], the definitions of 𝚋() and 𝚎() imply 𝚋(j+1)=𝚋(j) and 𝚎(j+1)=𝚎(j), and hence 𝙲𝚊𝚗𝚍𝚝c(𝚋(j+1),𝚎(j+1))=j. So, we have 𝙱𝚆𝚃[j]𝙱𝚆𝚃[j+1] and 𝙲𝚊𝚗𝚍𝚝c(𝚋(j+1),𝚎(j+1))=j, completing the proof.

4.3.3 Sufficiency: 𝙵𝚞𝚕𝚕𝙻𝚛𝚎𝚜𝚞𝚕𝚝

Throughout this section, let x be any index with 1<xn such that 𝙱𝚆𝚃[x]𝙱𝚆𝚃[x1]. Let c{𝙱𝚆𝚃[x],𝙱𝚆𝚃[x1]}, and define x,c:=𝙲𝚊𝚗𝚍𝚝c(𝚋(x),𝚎(x)), so x,c[𝚋(x),𝚎(x)).

By Definition 7, it suffices to prove that n𝚂𝙰[x,c]+1𝚛𝚎𝚜𝚞𝚕𝚝c. To this end, we first prove in Lemma 18 that the triple (n𝚂𝙰[x,c]+1,c,𝚠(x,c)) is added to 𝚛𝚘𝚠𝙻𝚒𝚜𝚝 in the x,c-th iteration of the algorithm. We then show in Lemma 19 that the tuple (n𝚂𝙰[x,c]+1,c,𝚠(x,c)) is removed from 𝚛𝚘𝚠𝙻𝚒𝚜𝚝 in the 𝚎(x,c)-th iteration and subsequently added to 𝚛𝚎𝚜𝚞𝚕𝚝c, which completes the proof. Finally, we present in Lemma 20 that the ordering of the indices in the output list is consistent with the suffixient array.

Lemma 18.

The tuple (n𝚂𝙰[x,c]+1,c,𝚠(x,c)) is added to the front of 𝚛𝚘𝚠𝙻𝚒𝚜𝚝 during the x,c-th iteration of the algorithm.

Proof.

Define {x,c,x,c+1} as follows: let =x,c if 𝚠(x,c)=𝙻𝙲𝙿[x,c], and =x,c+1 otherwise. By construction, 𝚠(x,c)=𝙻𝙲𝙿[], and at iteration x,c we have 𝚌𝚞𝚛𝚛_𝚋=𝚋().

Since {x,c,x,c+1}, it follows that 𝚋()x,c. Moreover, by Observation 6, 𝚠(x,c)𝙻𝙲𝙿[x]. Together with 𝚠(x,c)=𝙻𝙲𝙿[], this implies 𝙻𝙲𝙿[]𝙻𝙲𝙿[x]. Because 𝚋(x)x,c, 𝚋()x,c, and 𝙻𝙲𝙿[]𝙻𝙲𝙿[x], we obtain 𝚋()𝚋(x). Consequently, x,c[𝚋(),x,c+1)[𝚋(x),𝚎(x)). Therefore, x,c=𝙲𝚊𝚗𝚍𝚝c(𝚋(),x,c+1). Since 𝚌𝚞𝚛𝚛_𝚋=𝚋() at iteration x,c, we conclude that x,c=𝙲𝚊𝚗𝚍𝚝c(𝚌𝚞𝚛𝚛_𝚋,x,c+1). Hence, the tuple (n𝚂𝙰[x,c]+1,c,𝚠(x,c)) is added to the front of 𝚛𝚘𝚠𝙻𝚒𝚜𝚝 during the x,c-th iteration.

Lemma 19 establishes that 𝚂𝙰[x,c]+1𝚛𝚎𝚜𝚞𝚕𝚝c, and thus 𝙵𝚞𝚕𝚕𝙻𝚛𝚎𝚜𝚞𝚕𝚝.

Lemma 19.

The position n𝚂𝙰[x,c]+1 belongs to 𝚛𝚎𝚜𝚞𝚕𝚝c.

Proof.

By Lemma 18, the tuple (n𝚂𝙰[x,c]+1,c,𝚠(x,c)) is added to the front of 𝚛𝚘𝚠𝙻𝚒𝚜𝚝 at the x,c-th iteration. Let j>x,c be the smallest index such that 𝙻𝙲𝙿[j]<𝚠(x,c). By Observation 6, we have 𝚠(x,c)𝙻𝙲𝙿[x], and by the definition of 𝚎(x), 𝙻𝙲𝙿[x]>𝙻𝙲𝙿[𝚎(x)]. Hence, 𝚠(x,c)>𝙻𝙲𝙿[𝚎(x)], which implies x,c<j𝚎(x).

We first show that the tuple (n𝚂𝙰[x,c]+1,c,𝚠(x,c)) remains in 𝚛𝚘𝚠𝙻𝚒𝚜𝚝 throughout all iterations j with x,c<j<j. Indeed, for any such iteration j, since 𝙻𝙲𝙿[j]𝚠(x,c), the tuple cannot be removed in Step 1 of the algorithm. In Step 2, if 𝙱𝚆𝚃[j]c, the tuple is unaffected, as only the tuple pointed to by 𝙼𝙰𝙿[𝙱𝚆𝚃[j]] may be updated. If 𝙱𝚆𝚃[j]=c, then 𝚠(j)𝚠(x,c), because x,c=𝙲𝚊𝚗𝚍𝚝c(𝚋(x),𝚎(x)) and x,c<j<j𝚎(x). Assuming for contradiction that j=𝙲𝚊𝚗𝚍𝚝c(𝚌𝚞𝚛𝚛_𝚋,j+1) at iteration j, we would have 𝚌𝚞𝚛𝚛_𝚋>x,c. Moreover, 𝙻𝙲𝙿[𝚌𝚞𝚛𝚛_𝚋]<𝚠(j), since either 𝚠(j)=𝙻𝙲𝙿[j] and 𝚌𝚞𝚛𝚛_𝚋=𝚋(j), or 𝚠(j)=𝙻𝙲𝙿[j+1] and 𝚌𝚞𝚛𝚛_𝚋=𝚋(j+1). This yields x,c<𝚌𝚞𝚛𝚛_𝚋j<j and 𝙻𝙲𝙿[𝚌𝚞𝚛𝚛_𝚋]<𝚠(j)𝚠(x,c), contradicting the minimality of j. Therefore, the tuple is not removed in any iteration j with x,c<j<j.

If jn, then the tuple is removed from 𝚛𝚘𝚠𝙻𝚒𝚜𝚝 in Step 1 at iteration j, as 𝙻𝙲𝙿[j]<𝚠(x,c), and n𝚂𝙰[x,c]+1 is appended to 𝚛𝚎𝚜𝚞𝚕𝚝c. Otherwise, no such index j exists and the tuple remains in 𝚛𝚘𝚠𝙻𝚒𝚜𝚝 after n iterations. Then, the tuple is removed in the final step and its position entry is added to 𝚛𝚎𝚜𝚞𝚕𝚝c. Hence, n𝚂𝙰[x,c]+1𝚛𝚎𝚜𝚞𝚕𝚝c, as claimed.

Lemma 20 specifies the ordering of the output list.

Lemma 20.

Let 𝚛𝚎𝚜𝚞𝚕𝚝={r1,r2,,rk}. Then, the prefixes T[1..r1], T[1..r2], , T[1..rk] are sorted in co-lexicographical order.

Proof.

Recall that 𝚂𝙰[1..n] is the suffix array of Trev[1..n]. Therefore, the prefixes T[1..n𝚂𝙰[1]],T[1..n𝚂𝙰[2]],,T[1..n𝚂𝙰[n]] are sorted in co-lexicographical order. This implies that for any character cΣ and any positions i,j𝚛𝚎𝚜𝚞𝚕𝚝c with i<j, we have T[1..n𝚂𝙰[i]]colexT[1..n𝚂𝙰[j]], that is, T[1..n𝚂𝙰[i]] is co-lexicographically smaller than T[1..n𝚂𝙰[j]]. Since T[n𝚂𝙰[i]+1]=T[n𝚂𝙰[j]+1]=c, it follows that T[1..n𝚂𝙰[i]+1]colexT[1..n𝚂𝙰[j]+1].

Moreover, for any characters c,cΣ with c<c, and for any positions i𝚛𝚎𝚜𝚞𝚕𝚝c and j𝚛𝚎𝚜𝚞𝚕𝚝c, we have T[1..n𝚂𝙰[i]+1]colexT[1..n𝚂𝙰[j]+1].

Combining the arguments above establishes the lemma.

Lemmas 17 and 19 together imply that the output list 𝚛𝚎𝚜𝚞𝚕𝚝 is exactly 𝙵𝚞𝚕𝚕𝙻. By Lemma 11, 𝚛𝚎𝚜𝚞𝚕𝚝 is a minimum-size suffixient set, and Lemma 20 shows that its indices are sorted consistently with the definition of suffixient array, completing the proof of the correctness.

4.4 The Adjusted Algorithm

We have shown that the algorithm in Section 4.2 always computes a suffixient array correctly. But, we have not specified how to check whether or not i=𝙲𝚊𝚗𝚍𝚝c(𝚌𝚞𝚛𝚛_𝚋,i+1) at iteration i, where c=𝙱𝚆𝚃[i]. In this section, we specify the procedures to verify i=𝙲𝚊𝚗𝚍𝚝c(𝚌𝚞𝚛𝚛_𝚋,i+1).

In the data structure part, we construct, in addition, an array 𝚙𝚛𝚎𝚟𝚆[1..σ] consisting of σ entries, where each entry is a pair of the form (index,weight) drawn from {1,,n}×{1,0,,n}, initially set to (0,1). At iteration i, for i>1, the following invariant maintains: For each cΣ, the field 𝚙𝚛𝚎𝚟𝚆[c].index stores the largest index j<i such that 𝚠(j)>1 and 𝙱𝚆𝚃[j]=c, and 𝚙𝚛𝚎𝚟𝚆[c].weight stores the corresponding value 𝚠(j). If no such index exists, the entry 𝚙𝚛𝚎𝚟𝚆[c] is set to (0,1).

It holds that i=𝙲𝚊𝚗𝚍𝚝c(𝚌𝚞𝚛𝚛_𝚋,i+1) if and only if at least one of the following conditions is satisfied: C1: 𝚠(i)>𝙻𝙲𝙿[i]; C2: 𝚠(i)>1 and 𝚙𝚛𝚎𝚟𝚆[c].index<𝚌𝚞𝚛𝚛_𝚋; or C3: 𝚙𝚛𝚎𝚟𝚆[c].weight<𝚠(i). Each condition can be checked in constant time in a one-pass setting. In the remainder of this section, we prove this equivalence.

Lemma 21.

If i=𝙲𝚊𝚗𝚍𝚝c(𝚌𝚞𝚛𝚛_𝚋,i+1) at iteration i, then one of C1-C3 holds.

Proof.

Since i=𝙲𝚊𝚗𝚍𝚝c(𝚌𝚞𝚛𝚛_𝚋,i+1)>1, Definition 5 implies that 𝚠(i)>1. If 𝚠(i)>𝙻𝙲𝙿[i], then C1 trivially holds. Otherwise, by Lemma 12, either 𝙱𝚆𝚃[j]=c for every j[𝚌𝚞𝚛𝚛_𝚋,i) or 𝙱𝚆𝚃[j]c for every j[𝚌𝚞𝚛𝚛_𝚋,i). In either case, observe that 𝚠(j)=1 for every j(𝚌𝚞𝚛𝚛_𝚋,i) with 𝙱𝚆𝚃[j]=c, so 𝚙𝚛𝚎𝚟𝚆[c].index𝚌𝚞𝚛𝚛_𝚋 at iteration i.

If 𝚙𝚛𝚎𝚟𝚆[c].index<𝚌𝚞𝚛𝚛_𝚋, then C2 holds. Otherwise, 𝚙𝚛𝚎𝚟𝚆[c].index=𝚌𝚞𝚛𝚛_𝚋, and 𝙱𝚆𝚃[𝚌𝚞𝚛𝚛_𝚋]=c. Applying Lemma 12, the fact 𝙱𝚆𝚃[𝚌𝚞𝚛𝚛_𝚋]=c implies that 𝙱𝚆𝚃[j]=c for every j[𝚌𝚞𝚛𝚛_𝚋,i), so 𝚙𝚛𝚎𝚟𝚆[c].weight=𝚠(𝚌𝚞𝚛𝚛_𝚋)=𝙻𝙲𝙿[𝚌𝚞𝚛𝚛_𝚋].

Let :=i if 𝚠(i)=𝙻𝙲𝙿[i]; otherwise, :=i+1, so 𝚌𝚞𝚛𝚛_𝚋=𝚋() at iteration i and 𝚠(i)=𝙻𝙲𝙿[]. As 𝙻𝙲𝙿[]>𝙻𝙲𝙿[𝚋()]=𝙻𝙲𝙿[𝚌𝚞𝚛𝚛_𝚋]=𝚙𝚛𝚎𝚟𝚆[c].weight, C3 holds.

Overall, one of three condition must hold, completing the proof.

Lemma 22.

If at iteration i at least one of the conditions C1C3 holds, then i=𝙲𝚊𝚗𝚍𝚝c(𝚌𝚞𝚛𝚛_𝚋,i+1).

Proof.

We consider the three conditions separately.

Case C1: 𝚠(𝒊)>𝙻𝙲𝙿[𝒊].

By the definition of 𝚠(), this implies 𝚠(i)=𝙻𝙲𝙿[i+1]>𝙻𝙲𝙿[i], and hence 𝚌𝚞𝚛𝚛_𝚋=𝚋(i+1)=i. Therefore, 𝙲𝚊𝚗𝚍𝚝c(𝚌𝚞𝚛𝚛_𝚋,i+1)=𝙲𝚊𝚗𝚍𝚝c(i,i+1)=i.

Case C2: 𝚠(𝒊)>𝟏 and 𝚙𝚛𝚎𝚟𝚆[𝒄].𝒊𝒏𝒅𝒆𝒙<𝚌𝚞𝚛𝚛_𝚋.

By the invariant maintained by the data structure 𝚙𝚛𝚎𝚟𝚆, this implies that 𝚠(j)=1 for every j[𝚌𝚞𝚛𝚛_𝚋,i); otherwise, 𝚙𝚛𝚎𝚟𝚆[c].index would be at least 𝚌𝚞𝚛𝚛_𝚋. Hence, i is the smallest index in [𝚌𝚞𝚛𝚛_𝚋,i] with nonnegative weight, and therefore 𝙲𝚊𝚗𝚍𝚝c(𝚌𝚞𝚛𝚛_𝚋,i+1)=i.

Case C3: 𝚙𝚛𝚎𝚟𝚆[𝒄].𝒘𝒆𝒊𝒈𝒉𝒕<𝚠(𝒊).

Suppose that neither C1 nor C2 holds, but C3 does. Thus, 𝚠(i)𝙻𝙲𝙿[i] and 𝚙𝚛𝚎𝚟𝚆[c].index𝚌𝚞𝚛𝚛_𝚋. Since 𝚠(i)𝙻𝙲𝙿[i], Lemma 12 implies that either 𝙱𝚆𝚃[j]=c for all j[𝚌𝚞𝚛𝚛_𝚋,i) or 𝙱𝚆𝚃[j]c for all such j. In both cases, every index j(𝚌𝚞𝚛𝚛_𝚋,i) with 𝙱𝚆𝚃[j]=c satisfies 𝚠(j)=1, which implies 𝚙𝚛𝚎𝚟𝚆[c].index=𝚌𝚞𝚛𝚛_𝚋. As 𝚙𝚛𝚎𝚟𝚆[c].weight=𝚠(𝚌𝚞𝚛𝚛_𝚋) and 𝚠(i)>𝚙𝚛𝚎𝚟𝚆[c].weight by C3, we have 𝙲𝚊𝚗𝚍𝚝c(𝚌𝚞𝚛𝚛_𝚋,i+1)=i.

4.5 The Complexity of the Adjusted Algorithm

We first analyze the working space. By Proposition 4, the monotone stack over 𝙻𝙲𝙿[1..n] contains 𝒪(h) triples and thus uses 𝒪(h) words of space, where h is the height of the suffix tree built over the reversed text. The doubly-linked list 𝚛𝚘𝚠𝙻𝚒𝚜𝚝, together with the arrays 𝙼𝙰𝙿[1..σ] and 𝚙𝚛𝚎𝚟𝚆[1..σ], requires 𝒪(σ) space. Finally, the lists 𝚛𝚎𝚜𝚞𝚕𝚝c for cΣ store at most χn positions in total, where χ is the size of the suffixient array. Hence, excluding the 𝚂𝙰, 𝙻𝙲𝙿, and 𝙱𝚆𝚃 arrays, the overall working space is 𝒪(χ+σ+h)=𝒪(χ+h), since σχ.

We now analyze the running time. The algorithm performs a single left-to-right scan of the arrays 𝚂𝙰, 𝙻𝙲𝙿, and 𝙱𝚆𝚃, executing n iterations in total. The operations 𝚠() and 𝚋() are invoked at most twice per iteration, yielding 𝒪(1) amortized time per iteration.

By Lemma 13, the triples in 𝚛𝚘𝚠𝙻𝚒𝚜𝚝 are maintained in non-increasing order of their weights. Thus, in Step 1 of the i-th iteration, each triple (ptext,c,w) with w>𝙻𝙲𝙿[i] can be identified and removed from the head of 𝚛𝚘𝚠𝙻𝚒𝚜𝚝 in constant time.

In the second step, at most one triple is removed from 𝚛𝚘𝚠𝙻𝚒𝚜𝚝 and at most one new triple is inserted at its front. Using the pointer stored in 𝙼𝙰𝙿[𝙱𝚆𝚃[i]], the triple to be removed can be located in constant time. Since at most one triple is added per iteration, at most n triples are added to and removed from 𝚛𝚘𝚠𝙻𝚒𝚜𝚝 over the entire execution.

After all n iterations, the remaining triples in 𝚛𝚘𝚠𝙻𝚒𝚜𝚝, if any, are enumerated in 𝒪(σ) time. Concatenating the lists 𝚛𝚎𝚜𝚞𝚕𝚝1,,𝚛𝚎𝚜𝚞𝚕𝚝σ to produce the output takes an additional 𝒪(σ) time. Overall, the total running time is 𝒪(n+σ)=𝒪(n), assuming σn.

Combining Lemmas 17, 19, and 20 with the analysis above, we obtain the following result.

Theorem 23.

By scanning the arrays 𝙱𝚆𝚃[1..n], 𝚂𝙰[1..n], and 𝙻𝙲𝙿[1..n] of the reversed input text T[1..n] over an alphabet of size σ in a single pass, one can construct a suffixient array of T[1..n] in 𝒪(n) time using 𝒪(χ+h) words of working space in the worst case, in addition to these arrays, where χ denotes the size of the suffixient array and h denotes the height of the suffix tree built over the reversed text.

References

  • [1] Mohamed Ibrahim Abouelhoda, Stefan Kurtz, and Enno Ohlebusch. Replacing suffix trees with enhanced suffix arrays. J. Discrete Algorithms, 2(1):53–86, 2004. doi:10.1016/S1570-8667(03)00065-0.
  • [2] Alfred V Aho and John E Hopcroft. The design and analysis of computer algorithms. Pearson Education India, 1974.
  • [3] Christina Boucher, Travis Gagie, Alan Kuhnle, Ben Langmead, Giovanni Manzini, and Taher Mun. Prefix-free parsing for building big BWTs. Algorithms Mol. Biol., 14(1):13:1–13:15, 2019. doi:10.1186/S13015-019-0148-5.
  • [4] Michael Burrows and David J. Wheeler. A Block-sorting Lossless Data Compression Algorithm. Technical Report SRC-TR-124, Digital Equipment Corporation, Palo Alto, CA, USA, May 1994.
  • [5] Davide Cenzato, Lore Depuydt, Travis Gagie, Sung-Hwan Kim, Giovanni Manzini, Francisco Olivares, and Nicola Prezza. Suffixient Arrays: a New Efficient Suffix Array Compression Technique, 2025. doi:10.48550/arXiv.2407.18753.
  • [6] Luc Devroye, Wojciech Szpankowski, and Bonita Rais. A Note on the Height of Suffix Trees. SIAM J. Comput., 21(1):48–53, 1992. doi:10.1137/0221005.
  • [7] Johannes Fischer, Veli Mäkinen, and Gonzalo Navarro. Faster entropy-bounded compressed suffix trees. Theoretical Computer Science, 410(51):5354–5364, 2009. doi:10.1016/J.TCS.2009.09.012.
  • [8] Michael L. Fredman and Dan E. Willard. Surpassing the Information Theoretic Bound with Fusion Trees. J. Comput. Syst. Sci., 47(3):424–436, 1993. doi:10.1016/0022-0000(93)90040-4.
  • [9] Alan Kuhnle, Taher Mun, Christina Boucher, Travis Gagie, Ben Langmead, and Giovanni Manzini. Efficient Construction of a Complete Index for Pan-Genomics Read Alignment. J. Comput. Biol., 27(4):500–513, 2020. doi:10.1089/CMB.2019.0309.
  • [10] Stefan Kurtz. Reducing the space requirement of suffix trees. Softw. Pract. Exp., 29(13):1149–1171, 1999. doi:10.1002/(SICI)1097-024X(199911)29:13\%3C1149::AID-SPE274\%3E3.0.CO;2-O.
  • [11] Heng Li and Richard Durbin. Fast and accurate long-read alignment with Burrows-Wheeler transform. Bioinform., 26(5):589–595, 2010. doi:10.1093/BIOINFORMATICS/BTP698.
  • [12] Udi Manber and Eugene W. Myers. Suffix Arrays: A New Method for On-Line String Searches. SIAM J. Comput., 22(5):935–948, 1993. doi:10.1137/0222058.
  • [13] Gonzalo Navarro, Giuseppe Romana, and Cristian Urbina. Smallest Suffixient Sets as a Repetitiveness Measure. CoRR, abs/2506.05638, 2025. doi:10.48550/arXiv.2506.05638.
  • [14] Massimiliano Rossi, Marco Oliva, Ben Langmead, Travis Gagie, and Christina Boucher. MONI: A pangenomic index for finding maximal exact matches. J. Comput. Biol., 29(2):169–187, 2022. doi:10.1089/CMB.2021.0290.
  • [15] Esko Ukkonen. On-Line Construction of Suffix Trees. Algorithmica, 14(3):249–260, 1995. doi:10.1007/BF01206331.
  • [16] Peter Weiner. Linear Pattern Matching Algorithms. In 14th Annual Symposium on Switching and Automata Theory, Iowa City, Iowa, USA, October 15-17, 1973, pages 1–11. IEEE Computer Society, 1973. doi:10.1109/SWAT.1973.13.

Appendix A The Pseudocode for Computing 𝚠(𝒊)

Let 𝚙𝚛𝚎_𝚋𝚠𝚝:=𝙱𝚆𝚃[x1] if x>1 and 1 otherwise, 𝚌𝚞𝚛𝚛_𝚋𝚠𝚝:=𝙱𝚆𝚃[x], and 𝚗𝚎𝚡𝚝_𝚋𝚠𝚝:=𝙱𝚆𝚃[x+1] if x<n and 1 otherwise. Similarly, let 𝚌𝚞𝚛𝚛_𝚕𝚌𝚙:=𝙻𝙲𝙿[x], and 𝚗𝚎𝚡𝚝_𝚕𝚌𝚙:=𝙻𝙲𝙿[x+1] if x<n, and 1 otherwise. The procedure Compute_𝚠 determines 𝚠(x) as Algorithm 1. By scanning the 𝙻𝙲𝙿[1..n] and 𝙱𝚆𝚃[1..n] arrays in a single pass, we can apply Algorithm 1 to compute 𝚠(x) for all x=1,2,.

Algorithm 1 Compute_𝚠(𝚙𝚛𝚎_𝚋𝚠𝚝,𝚌𝚞𝚛𝚛_𝚋𝚠𝚝,𝚗𝚎𝚡𝚝_𝚋𝚠𝚝,𝚌𝚞𝚛𝚛_𝚕𝚌𝚙,𝚗𝚎𝚡𝚝_𝚕𝚌𝚙).

Appendix B The Pseudocode for Computing 𝚋(𝒊)

Algorithm 2 Initial-Stack().
Algorithm 3 Compute_b(S,𝙻𝙲𝙿[i],i).

Appendix C The Pseudocode and an Example for Computing Suffixient Arrays

Algorithm 4 Compute Suffixient Arrays(𝙱𝚆𝚃[1..n],𝚂𝙰[1..n],𝙻𝙲𝙿[1..n]).
Table 1: Execution trace of the algorithm computing a suffixient array for the input T=AGCACAGCA$. The table provides Trev[1..n], the 𝙻𝙲𝙿,𝙱𝚆𝚃, and 𝚂𝙰 arrays, the variable curr_b, the contents of 𝚛𝚘𝚠𝙻𝚒𝚜𝚝, and the character-specific result lists Resultc. The final algorithm output is [10,1,5,7] as the suffixient array.
𝒊 1 2 3 4 5 6 7 8 9 10 n+1
T[i] A G C A C A G C A $
Trev[i] A C G A C A C G A $
𝙻𝙲𝙿[i] -1 0 1 2 4 0 1 3 0 2
𝙱𝚆𝚃[i] A G G C $ A A A C C
𝚠(i) 0 0 2 4 4 0 -1 0 0 -1
𝚂𝙰[i] 10 9 4 6 1 5 7 2 8 3
n𝚂𝙰[i]+1 1 2 7 5 10 6 4 9 3 8
𝙻𝙲𝙿[i]=𝚠(i)? F T F F T T F F T F
curr_b 1 1 3 4 4 1 6 1 1 9
𝚠(i)>1? T T T T T T F T T F
i=𝙲𝚊𝚗𝚍𝚝c(curr_b,i+1) T T T T T F N/A F F N/A
𝚛𝚘𝚠𝙻𝚒𝚜𝚝 (1,A,0) (2,G,0) (7,G,2) (5,C,4) (10,$,4) (1,A,0) (1,A,0) (1,A,0) (1,A,0) (1,A,0)
(1,A,0) (1,A,0) (7,G,2) (5,C,4)
(1,A,0) (7,G,2)
(1,A,0)
𝚛𝚎𝚜𝚞𝚕𝚝$ 10 10 10 10 10 10 10
𝚛𝚎𝚜𝚞𝚕𝚝A 1
𝚛𝚎𝚜𝚞𝚕𝚝C 5 5 5 5 5 5 5
𝚛𝚎𝚜𝚞𝚕𝚝G 7 7 7 7 7 7 7