Abstract 1 Introduction 2 Preliminaries 3 Problem definition and overview of solution 4 Non-Crossing Matches 5 Crossing Matches References Appendix A Proofs from Section 2 Appendix B Proofs from Section 3 Appendix C Crossing matches: Naive algorithm Appendix D Proofs from Section 4 Appendix E Proofs from Section 5

Optimal Structure for Prefix-Substring Queries

Paweł Gawrychowski ORCID Faculty of Mathematics and Computer Science, University of Wrocław, Poland    Florin Manea ORCID Department of Informatics, University of Göttingen, Germany    Jonas Richardsen ORCID Department of Informatics, University of Göttingen, Germany
Abstract

The prefix-substring matching problem [Gu, Farach, and Beigel, SODA 1994] consists in preprocessing a string s of length n for the following queries: given a triple (i,j,k){0,,|s|}3 with 1jk, representing a prefix s[1:i] and a substring s[j:k] of s, find the longest prefix of s that is a suffix of s[1:i]s[j:k]. This is an useful primitive in e.g. dynamic text indexing, compressed pattern matching, and pattern matching on block graphs. The border tree uses some basic periodicity properties to answer such queries in 𝒪(logn) time after 𝒪(n) time preprocessing of s. We design a linear-space structure that answers such queries in constant time after 𝒪(n) time preprocessing of s over a polynomial alphabet, which is worst-case optimal.

Keywords and phrases:
Border Tree, Prefix-Substring Query, Data Structures
Copyright and License:
[Uncaptioned image] © Paweł Gawrychowski, Florin Manea, and Jonas Richardsen; licensed under Creative Commons License CC-BY 4.0
2012 ACM Subject Classification:
Theory of computation Pattern matching
Funding:
Paweł Gawrychowski is partially supported by the Polish National Science Centre grant number 2023/51/B/ST6/01505. The work of Florin Manea and Jonas Richardsen was partly supported by the German Research Foundation (DFG) through the Heisenberg project 466789228 and the research project 562495345.
Editors:
Philip Bille and Nicola Prezza

1 Introduction

It is usual for papers in the area of algorithms on strings to start with preprocessing the input string to support some standard queries. Gu, Farach, and Beigel [16] defined the following prefix-substring matching problem that consists in preprocessing a given string s of length n for the following queries: given (i,j,k){0,,|s|}3 with 1jk, representing a prefix s[1:i] and a substring s[j:k] of s, find the longest prefix of s that is a suffix of s[1:i]s[j:k]. The original motivation behind such queries was maintaining a dynamic indexing data structure. Using some basic periodicity properties, namely the fact that all borders of a given string of length at most n can be decomposed into 𝒪(logn) arithmetic progressions, the authors were able to design a data structure that answers prefix-substring queries in 𝒪(logn) time after 𝒪(n) time preprocessing. Their structure has found quite a few other applications, for example it has been used in to design an efficient algorithm for pattern matching in LZW and LZ compressed texts [3, 10] to obtain algorithms working in 𝒪(nlogm+m) and 𝒪(nlog2(N/n)+m) time, respectively (where n is the size of the compressed representation, N is the length of the text, and m is the length of the pattern).

For pattern matching in both LZW and LZ compressed texts, Gawrychowski designed faster algorithms working in 𝒪(n+m) and 𝒪(nlog(N/m)+m) time, respectively [14, 15]. The starting point in both results is an improved data structure for the related prefix-suffix matching problem, in which the query consists of (i,j){0,,|s|}3, representing a prefix s[1:i] and a suffix s[j:n] of s, and asks for detecting an occurrence of the whole s in s[1:i]s[j:n]. A simple but useful observation is that such an occurrence corresponds to a long border of either s[1:i] or s[j:n], where the long borders of t[1:] are b{/2,,} such that t[1:b]=t[(b+1):]. By basic properties of periodicity, all long borders form a single arithmetic progression, which allows for answering such a query in constant time after 𝒪(n) time preprocessing. However, such a simple approach does not suffice to answer the more difficult prefix-substring queries in the same complexity, and both algorithms need to follow a more indirect route to achieve the improved time complexity: for LZW this requires amortizing the time complexity over the subsequent queries [14], and for LZ batching similar queries after an initial transformation of the input [15]. A later paper by Ganardi and Gawrychowski for pattern matching in SLP compressed texts [13] (which can be seen as a special case of LZ compression) avoids using prefix-substring queries altogether by, informally speaking, over-approximating the longest prefix with constantly many substrings of the pattern.

Pissis [19] observed that any data structure for answering longest common extension queries can be used to answer prefix-suffix queries. By plugging in the best known result, this results in a data structure with constant query time and 𝒪(n/logσn) preprocessing time, for a string of length n over an integer alphabet {1,,σ}. He also observed that such queries are very useful in pattern matching on node-labeled graphs, see e.g. [5, 8, 4, 2]).

Thus, the complexity of prefix-suffix queries is well understood. This is certainly not the case for prefix-substring queries, though, with the best bound being 𝒪(logn) query after 𝒪(n) preprocessing [16]. While in the applications related to compressed pattern matching it was possible to avoid the extra factor of logn by exploiting the additional structure of the queries, this has led to certain technical complications that would best be avoided. Further, for the applications related to pattern matching on node-labeled graphs such additional structure of the queries is less clear. It would hence be desirable to design a structure with 𝒪(1) query time after linear time preprocessing. It is a priori not clear if such a structure exists: it may be the case that, say, prefix-substring matching is as difficult as predecessor search, leading to a lower bound of Ω(loglogn) for structures of size 𝒪~(n).

We show how to answer prefix-substring queries in constant time with a structure of size 𝒪(n) that can be constructed in 𝒪(n) time for a string of length n over a polynomial alphabet (which is a standard assumption in the area; otherwise, the construction time increases to 𝒪(nlogn) due to sorting). We emphasise that, as observed in prior work, this allows for finding the longest prefix of s that is a suffix of s[i:]s[j:k], for (i,,j,k){0,,|s|}4: we simply retrieve first the answer h to the prefix-substring query (0,i,), and then retrieve the answer to the query (h,j,k).

Our paper is structured as follows: we introduce the basic notions and data structures in Section 2, then give an overview of the considered problem and of our approaches in Section 3. In particular, we present a sequence of solutions for the problem, starting with simple ones (to set a baseline) and continuing with more involved and efficient ones (which, on the one hand, highlight potential obstacles one may meet when using certain techniques, and, on the other hand, provide us with deeper insights in the combinatorics of the problem), and conclude with the optimal solution. The technical details are presented in the rest of the paper (and in the appendix, for space reasons).

2 Preliminaries

Let be the set of strictly positive integer numbers and, for n, let [n]:={1,,n}. For an alphabet Σ, a string s of length n (over Σ) is a concatenation of characters s[1]s[2]s[n] with s[i]Σ for i[n]. For i,j[n], define s[i:j]:=s[i]s[j] for ij and s[i:j]:=ε otherwise (where ε is the unique string of length 0). We call s[i:j] a substring of s and define Substr(s):={s[i:j]i,j[|s|]} to be the set of all substrings of s. As a shorthand, we write s[i:] and s[:j] instead of s[i:|s|] and s[1:j] respectively, where |s| denotes the length of s. Furthermore, we define sR:=s[|s|]s[|s|1]s[1] to be the mirror image of s and, for m, let sm be the string obtained by repeating s exactly m times.

Borders and periodicity.

For strings r and s with |r||s| we say that r is a prefix (respectively, suffix) of s, if and only if r=s[:|r|] (respectively, r=s[|s||r|+1:]); r is a border of s, if and only if it is both a prefix and a suffix of s. We define Pref(s):={ε}{s[:i]i[|s|]}, Suff(s):={ε}{s[i:]i[|s|]} and Bord(s):=Pref(s)Suff(s) as the sets of prefixes, suffixes and borders of s respectively. For y[|s|], we say that the prefix s[:y] is the prior of the suffix s[y+1:] and that the suffix s[y+1:] is the posterior of the prefix s[:y].

Given some p<|s|, we say that s is p-periodic, if and only if s[i]=s[i+p] for all i[|s|p]. Note that this is equivalent to the condition s[:|s|p]=s[1+p:], meaning that s is p-periodic if and only if s has a border of length |s|p. We say that the period per(s) of s is the smallest value of p such that s is p-periodic or if there is no such value. In the latter case, we also say that s is aperiodic. The following lemma is well-known [18].

Lemma 1 (Periodicity lemma).

If a string s of length n is both p-periodic and q-periodic and p+qn, then s is also gcd(p,q)-periodic.

Using this lemma, we can derive the following properties about borders and their periods. The proofs of these three results are given in Appendix A, for completeness.

Lemma 2.

Let s be a string of length n with p=per(s) and let m[n1] such that s[:m] is a border of s with per(s[:m])=qp. Then p12m and n32m.

Lemma 3.

Let s be a string of length n. Then |{per(r)rBord(s)}|𝒪(logn).

Let Bordp(s):={rrBord(s),per(r)=p} denote the set of borders of s with period p.

Lemma 4.

For string s, with |s|=n, and integer p[n1], the elements of {|r|rBordp(s)} form an arithmetic progression with step p. For p=, Bordp(s) contains at most one non-empty string.

Computational model.

We assume the standard unit-cost word RAM with logarithmic word-size ω (so each memory word consists of ωlogn bits). Standard arithmetical and bitwise operations (and indirect addressing) on such words are assumed to take 𝒪(1) time [12]. We assume that our input is over a polynomial alphabet, i.e., |Σ|n𝒪(1), where n is the length of the input string. We refer to a structure that can be stored in 𝒪(n) memory words as linear-space.

Longest common prefix.

For string s, with |s|=n, and i,j[n], let LCPs(i,j):=max{|r|rPref(s[i:])Pref(s[j:])} be the length of the longest common prefix of s[i:] and s[j:].

Lemma 5 ([17]).

Given a string s of length n, we can construct a linear-space data structure in 𝒪(n) time allowing us to answer LCPs(i,j) queries for any i,j[n] in constant time.

Using this data structure, we can also find longest periodic prefixes. Given some p[n1], we can calculate =LCPs(i,i+p), if >0 we have s[i:i+1]=s[i+p:i++p1] and s[i+]s[i+p+], meaning that s[i:i++p1] is the longest p-periodic prefix of s[i:]. By constructing the data structure for sR, we can similarly determine longest common suffixes and longest periodic suffixes.

Suffix tree.

For some string s of length n, the suffix tree of s is a compressed trie T=(V,E), i.e., a tree with the following properties:

  • Each edge is labeled with some non-empty string over the alphabet Σ$ with $Σ being a special character.

  • The labels of any two edges starting at the same node do not share a common prefix.

  • Each inner node has at least two children.

Let vV be some node and let s1,,sm be the labels of the path from the root to v. We now define Strings(v):={s1sm1rrPref(sm){ε}} as the set of strings corresponding to v, for the root node vr it is Strings(vr)={ε}. The defining property of the suffix tree is now that vVStrings(v)=Substr(s$). We use the following results for the suffix tree:

Theorem 6 ([9]).

The suffix tree of a string s, with |s|=n, is constructed in 𝒪(n) time.

For any i,j[n], there is exactly one node v in the suffix tree of s such that s[i:j]Strings(v). We write v=SuffNodes(i,j) and call v the node corresponding to the substring s[i:j].

Lemma 7 ([6]).

Given a string s of length n, we can construct a linear-space data structure in 𝒪(n) time allowing us to find SuffNodes(i,j) for any i,j[n] in constant time.

We also use the following standard observation:

Observation 8.

Let s be a string, let u and v be nodes in the suffix tree of s and let suStrings(u) and svStrings(v) with |su||sv| (if uv this condition can be omitted). Then su is a prefix of sv if and only if u is an ancestor of v.

In this paper, we take the relations “is ancestor of” and “is descendant of” to be reflexive, i.e., a node is both its own ancestor and its own descendant, unless explicitly noted otherwise.

The following result is shown in Appendix A.

Lemma 9.

Let s be a string, let rSuff(s) be a suffix of s and v be the node in the suffix tree of s corresponding to r. Then |r||r| for all rStrings(v)Substr(s) (this excludes all strings ending with $) and r is the only suffix of s corresponding to v.

Border tree.

For some string s of length n, the border tree of s is defined as follows. The nodes of the tree correspond one-to-one to the prefixes of s and for any node v corresponding to some prefix rε, the parent of v is the node that corresponds to the longest border of r. This is well defined, because Bord(r) always contains at least ε and any border of r is a prefix of r and therefore also a prefix of s. The node corresponding to ε has no parent and is the root node of the tree. The border tree can now be compressed by grouping the borders by their periodicities (on each path). From Lemma 4 it follows that we can encode each group of borders as an arithmetic progression using constant space. The compressed border tree then has depth 𝒪(logn) as seen in Lemma 3.

Lemma 10 ([16]).

Given a string s of length n, we can construct both the uncompressed and compressed border tree of s in 𝒪(n) time.

From the definition of the border tree, it follows that for some prefix rPref(s), the ancestors of the node corresponding to r in the border tree correspond exactly to the borders of r. For the compressed version of the border tree, this is not true anymore. Consider some section on some path, where all nodes correspond to prefixes with the same periodicity. During compression, this section is replaced with a single node. In this process, all nodes that were children of any node in the section become children of the new compressed node, even though they have different sets of ancestors. In particular, from the arithmetic progression of prefixes that is represented by their new parent, only the prefixes up to some length are actual borders. We can determine this length, for some node v, by taking the smallest prefix r associated with v, the longest border of which has to have length |r|per(r), we can then trim the arithmetic progression corresponding to the parent of v, removing all prefixes longer than this value.

Induced nodes and partner query.

Given two trees T1=(V1,E1) and T2=(V2,E2), we can relate these trees to each other by using a common set of labels. Hereby, each label gets assigned to one node from each tree. We then say that two nodes v1V1 and v2V2 are induced, if and only if there are descendants u1 of v1 and u2 of v2 that share the same label. The partner of v1 with respect to v2, denoted partner(v1/v2), is the lowest ancestor v2 of v2, such that v1 and v2 are induced.

Lemma 11 ([1]).

Given trees T1=(V1,E1) and T2=(V2,E2), we can construct data structures allowing us to compute partner(v1/v2) for any (v1,v2)V1×V2 in

  • 𝒪(logδn) time for any constant δ>0, for a linear-space data structure constructed in 𝒪(n) time.

  • 𝒪(loglogn) time, for a data structure constructed in 𝒪(nloglogn) time and space.

Micro-macro decomposition.

Given a tree, we can decompose it, as follows. First, we select a subset of the nodes from the tree, including the root node. We call these nodes special. For each special node u, we partition its children into non-empty sets. Each set of siblings corresponds to one fragment of the decomposition, containing the siblings, their parent u (which becomes the root node of the fragment), and all their descendants which have u as their lowest special ancestor. Since the root node is special, each node has a special ancestor and each non-special node belongs to exactly one fragment. Therefore, we obtain a set of micro-trees (the fragments) and a macro-tree that consists only of the special nodes, arranged as to preserve the ancestor/descendant relations between each pair of node. It is possible to construct such a decomposition for any desired fragment size efficiently (even though this is well known – see [11], for instance – we give a proof in Appendix A, fitting to our setting, for completeness):

Lemma 12.

Given a tree of size n and some z[n], it is possible to find a micro-macro decomposition that has 𝒪(n/z) fragments of size at most z in 𝒪(n) time.

Other data structures.

We also use the following well known data structures.

Lemma 13 (RMQ [7]).

For n and list of numbers i1,,in, we can construct a linear-space data structure in 𝒪(n) time allowing us to retrieve max{ikk[a,b]} for any a,b[n],ab in 𝒪(1) time.

Lemma 14 (Fusion tree [12]).

Given n and sets S1,S2,,Sk[n] such that, for every i, |Si|𝒪(logn), we can construct in 𝒪(n+i|Si|) time and space a data structure allowing us to determine the predecessor and successor of k in any Si, i.e., max{mSimk} and min{mSimk}, for any k[n] in 𝒪(1) time.

3 Problem definition and overview of solution

Definition 15.

For string s, with |s|=n, we call (i,j,k)[n]3, with jk, a prefix-substring query and denote by

PrefSubstrQueries(s):={(i,j,k)[n]3jk}

the set of all queries111To focus on non-trivial prefix-substring queries, we exclude the case i=0 in this definition. This case is discussed at the end of Section 3.. For (i,j,k)PrefSubstrQueries(s), we define

PrefSubstrMatchess(i,j,k):=Pref(s)Suff(s[:i]s[j:k]);

an element rPrefSubstrMatchess(i,j,k) is a match for the query (i,j,k). Furthermore, we write

PrefSubstrs(i,j,k):=max{|r|rPrefSubstrMatchess(i,j,k)}

to refer to the length of the longest match. This is well-defined, since ε is always a match.

The problem PrefixSubstringQueries is now defined as follows: preprocess an input string s such that we can answer prefix-substring queries for s. For each given query (i,j,k), we need to return PrefSubstrs(i,j,k) (in an online fashion, before reading the next query).

Note that the answer to PrefixSubstringQueries can be used to efficiently reconstruct the set of all matches, using the following property (proven in Appendix B):

Lemma 16.

For any string s and any query (i,j,k)PrefSubstrQueries(s), the answer x=PrefSubstrs(i,j,k) satisfies PrefSubstrMatchess(i,j,k)=Bord(s[:x]).

Consider now some element s[:x] of PrefSubstrMatchess(i,j,k). This is a suffix of s[:i]s[j:k]. If |s[:x]||s[j:k]|, then s[:x] is a suffix of s[j:k] and consequently, s[:x]PrefSubstrMatchess(i,j,k) for all i[|s|]. Otherwise, it “crosses” over into s[:i], so it is not independent of i. Let us formalize this notion:

Definition 17.

Let s be a string and let (i,j,k)PrefSubstrQueries(s) be a query. Then we write

PrefSubstrMatchessc(i,j,k):={uPrefSubstrMatchess(i,j,k)|u|>|s[j:k]|}

to denote the set of crossing matches and

PrefSubstrMatchessnc(i,j,k):=PrefSubstrMatchess(i,j,k)PrefSubstrMatchessc(i,j,k)

to denote the set of non-crossing matches.

We also define PrefSubstrsc(i,j,k) and PrefSubstrsnc(i,j,k) to be the lengths of the longest elements of PrefSubstrMatchessc(i,j,k) and PrefSubstrMatchessnc(i,j,k) respectively. If the former is empty, we set PrefSubstrsc(i,j,k)=0.

We will solve PrefixSubstringQueries by separately finding the length of the longest crossing and the length of the longest non-crossing match, it is then PrefSubstrs(i,j,k)=max(PrefSubstrsnc(i,j,k),PrefSubstrsc(i,j,k)). This is outlined in the following paragraphs.

Non-crossing matches.

Clearly, the non-crossing matches are exactly the borders of s[:k] of length at most |s[j:k]|. Thus, determining the length of the longest non-crossing match, PrefSubstrsnc(i,j,k), is equivalent to determining the length of the longest such border of s[:k]. Each such match s[:x] satisfies that (s[:x])R is a prefix of (s[j:k])R and a suffix of sR, so the longest match can then be found in the suffix tree of sR as the lowest ancestor of the node corresponding to (s[j:k])R, that corresponds to a suffix. Determining this is possible in 𝒪(1) time.

Crossing matches: simple solutions.

For crossing match s[:x]PrefSubstrMatchessc(i,j,k), we can split the match at y=x|s[j:k]|, such that s[:y] is a suffix (and as such, a border) of s[:i] and s[y+1:] has prefix s[j:k]. We then say that s[:y] induces the match s[:x]. Like this, PrefixSubstringQueries can be rephrased as: “Find the longest border s[:y] of s[:i] such that its posterior starts with s[j:k]”. By checking, for each border s[:y] of s[:i] individually, whether s[y+1:] has prefix s[j:k], we obtain a naive algorithm that answers queries in 𝒪(n) time, after 𝒪(n) time preprocessing. This can be optimized by exploiting the structure of the borders, yielding an algorithm with 𝒪(logn) query time, after 𝒪(n) preprocessing.

Crossing matches: reduction to partner query.

Sticking with our approach to find the longest y[n], such that s[:y] is a border of s[:i] and s[y+1:] has prefix s[j:k], we observe that this can be expressed as a problem on the border and suffix tree, as follows. Labeling the node vy corresponding to s[:y] in the border tree and the node SuffNodes(y+1,n) in the suffix tree with label y for each y[n], we then want to find the largest label y such that the node labeled with y in the border tree is an ancestor of the node corresponding to s[:i] and the node labeled with y in the suffix tree is a descendant of SuffNodes(j,k). This is quite similar to solving partner(SuffNodes(j,k)/vi)), only that the result vi of this query is only guaranteed to have a descendant with a label y that also occurs in the descendants of SuffNodes(j,k), instead of having such a label itself. Still, it turns out that the matches are exactly the borders of s[:y+t] of length at most i+t, meaning that the longest match can be found similarly as the non-crossing matches. Apart from solving the partner query, this reduction only requires linear preprocessing and constant query time.

Crossing matches: final algorithm.

To obtain a constant time solution for crossing matches, we split the borders s[:y] of s[:i] into three groups:

  • For yt:=|s[j:k]|, the set of such y satisfying that the posterior s[y+1:] has prefix s[j:k] is an arithmetic progression with some step p. We can find the longest such s[:y] that induces a match by comparing the lengths of the longest p-periodic suffixes of s[:i]s[j:k] and s[:y+t] for all such y, using the fact that each s[:y+t] has a different such length.

  • For y>t and per(s[:y])<t, we observe that the borders s[:y] of s[:i] with this condition only have a constant number of different periodicities. For each periodicity p, we can in constant time determine the longest induced match, using the same method with which we optimized the initial naive algorithm.

  • For per(s[:y])t, we see that almost all (all, but the longest) prefixes s[:y] with some shared periodicity p=per(s[:y])t share a common prefix of length t and therefore, for each such periodicity p, we only need to consider two candidates for inducing the longest match and in total, we consider 𝒪(logn) candidates. If we sort them in the preorder of their respective posteriors in the suffix tree, all candidate prefixes that induce a match lie in some interval of this list, corresponding to the preorder interval representing the subtree rooted in SuffNodes(j,k). We can then find this interval using fusion trees and the longest match in the interval using an RMQ query.

While this enables us to answer queries in constant time, the last case requires maintaining 𝒪(logn)-sized data structures for each prefix of s. To avoid this, we perform a micro-macro decomposition of the border tree with fragment size logn and then only build these data structures for the prefixes corresponding to the 𝒪(n/logn) special nodes. For some query (i,j,k)PrefSubstrQueries(s), we find the fragment containing the node vi corresponding to s[:i] and its special root ui. We then check all borders corresponding to ancestors of ui, using the method from above. The remaining borders of s[:i] correspond to ancestors of vi in that fragment. The subset of these borders that induce a match can be expressed as an interval in 3D space, with the dimensions being the preorder and postorder indices of the corresponding nodes in the fragment and the preorder index of the posterior in the suffix tree. Since there are at most 𝒪(logn) such borders, we can encode sets of borders in a constant number of machine words and use bit operations to operate on the sets. This allows for an 𝒪(1) time solution, using 𝒪(logn) preprocessing per fragment, i.e., 𝒪(n) preprocessing time in total.

Combining this with Lemma 20, addressing non-crossing matches, we obtain the following.

Theorem 18.

For string s, with |s|=n, we can construct a linear-space data structure in 𝒪(n) time allowing us to determine PrefSubstrs(i,j,k) for any (i,j,k)PrefSubstrQueries(s) in 𝒪(1) time.

Note that we only consider here queries (i,j,k) with i1. In fact, our algorithm addressing non-crossing matches can be canonically used to answer queries (0,j,k), so the statement of Theorem 18 holds also for all queries of the form (0,j,k), with 1jkn.

4 Non-Crossing Matches

Let us start with the non-crossing matches, which, as we will see, are the easier of the two kinds of matches. As hinted in the overview, it is

PrefSubstrMatchessnc(i,j,k) =PrefSubstrMatchess(i,j,k)PrefSubstrMatchessc(i,j,k)
={uPrefSubstrMatchess(i,j,k)|u||s[j:k]|}
={uPref(s)Suff(s[:i]s[j:k])|u||s[j:k]|}
={uPref(s)Suff(s[j:k])|u||s[j:k]|}
={uPref(s[:k])Suff(s[:k])|u||s[j:k]|}
={uBord(s[:k])|u||s[j:k]|}

Therefore, the longest non-crossing match will be the longest border of s[:k] of length at most |s[j:k]|. To find this, we use the following lemma, shown in Appendix D:

Lemma 19.

Given string s, with |s|=n, we can construct a linear-space data structure in 𝒪(n) time allowing us to determine max{|r|rBord(s[:k]),|r|} for any k,[n] in 𝒪(1) time.

Proof sketch.

We construct the suffix tree of sR. The desired solution x satisfies that (s[:x])R is the longest suffix of sR that is also a prefix of (s[j:k])R. In terms of the suffix tree, determining this corresponds to finding the lowest ancestor of SuffNodesR(nk+1,nj+1) that corresponds to a suffix of s; this ancestor can be precomputed for every node.

By setting =|s[j:k]|=kj+1, we obtain the following lemma.

Lemma 20.

For string s, with |s|=n, we can construct a linear-space data structure in 𝒪(n) time allowing us to determine PrefSubstrsnc(i,j,k) for any (i,j,k)PrefSubstrQueries(s) in 𝒪(1) time.

5 Crossing Matches

Now let us turn to crossing matches, and begin with some simple solutions, which set a baseline for this problem. For a crossing match s[:x]PrefSubstrMatchessc(i,j,k) we can split the match at y=x|s[j:k]|, such that s[:y] is a suffix (and as such, a border) of s[:i] and s[y+1:] has prefix s[j:k]. As already defined in the overview, we then say that s[:y] induces the match s[:x] and try to find the longest border s[:y] of s[:i] such that its posterior starts with s[j:k]. Checking each border of s[:y] individually yields a naive algorithm, answering queries in 𝒪(n) time after 𝒪(n) preprocessing (see Lemma 32 in Appendix).

We saw in Lemma 3 and Lemma 4 that Bord(s[:i]) can be partitioned into 𝒪(logn) arithmetic progressions of borders. It turns out that we can check each of these progressions of borders in 𝒪(1) time (see Appendix E for the proof).

Lemma 21.

Let s be a string of length n. We can construct a linear-space data structure in 𝒪(n) time allowing us to find the largest y satisfying s[:y]Bordp(s[:i]) and s[j:k]Pref(s[y+1:]) for any query (i,j,k)PrefSubstrQueries(s) and periodicity p[i1]{} in constant time, provided we are given (an 𝒪(1) size representation of) Bordp(s[:i]) as well.

Now it suffices to just check all groups of borders to find the solution (see Appendix E):

Lemma 22.

For string s, with |s|=n, we can construct a linear-space data structure in 𝒪(n) time allowing us to retrieve PrefSubstrsc(i,j,k) for (i,j,k)PrefSubstrQueries(s) in 𝒪(logn) time.

5.1 Reduction to Partner Query

We can now focus on more efficient (yet not optimal) solutions. As derived previously, the problem can be phrased as finding the longest border s[:y] of s[:i], such that the posterior s[y+1:] has s[j:k] as a prefix. We can frame this problem in terms of the uncompressed border and suffix tree as follows: Consider the border and suffix trees for s. For any t[n], let vt be the node corresponding to s[:t] and label both vt in the border tree and SuffNodes(t+1,n) in the suffix tree with label t. The necessary conditions on s[:y] can now be expressed in terms of the suffix and border tree:

  • s[:y] is a suffix/border of s[:i], if and only if the node labeled with y in the border tree is an ancestor of the node corresponding to vi.

  • s[y+1:] must have s[j:k] as a prefix, i.e., the node labeled with y in the suffix tree must be a descendant of SuffNodes(j,k).

Therefore, the largest such y can be determined by finding the lowest ancestor of vi in the border tree, labeled with some label y, such that there is also a descendant of SuffNodes(j,k) in the suffix tree with the same label. This can be reduced to a partner query.

Lemma 23.

If we can construct a data structure in time P allowing to answer any partner query on two trees of size 𝒪(n) for some n in time Q, then, given a string s of length n, we can create a data structure in time P+𝒪(n) allowing us to determine PrefSubstrsc(i,j,k) for any query (i,j,k)PrefSubstrQueries(s) in Q+𝒪(1) time.

Proof.

See Figure 1 for an illustration of the proof.

Figure 1: Sketch for the proof of Lemma 23. In the top part, we have the suffix tree and border tree, reduced to the relevant nodes, showing the ancestor/descendant relations between them (by dashed edges). In the bottom part, we see how these relations translate into prefix/suffix/border relations between the respective substrings, with the vertical lines representing equality of substrings.

During preprocessing, we construct the border and suffix tree, both labeled as described above, and data structures to answer partner queries on the trees and SuffNodes queries. We also construct the data structure necessary for Lemma 19.

When given a query (i,j,k)PrefSubstrQueries(s), we initially determine the node partner(SuffNodes(j,k)/vi) in the border tree. The resulting node corresponds to some prefix s[:i]. We know that uj,k:=SuffNodes(j,k) and vi are induced, i.e, we can find a label y such that there are descendants of each uj,k and vi with this label. More precisely, the node uy+1:=SuffNodes(y+1,n) in the suffix tree and the node vy in the border tree are descendants of uj,k and vi respectively.

Consider now the label y, representing the unknown solution, and the corresponding nodes SuffNodes(y+1,n) and vy in suffix and border tree, respectively. We know that SuffNodes(y+1,n) must be a descendant of uj,k, therefore, uj,k and vy are induced. Because the partner query finds the lowest induced ancestor, vy cannot be lower than vi on the path from the root to ui and it must be yi. Similarly, the solution x=PrefSubstrsc(i,j,k) must satisfy xi+t with t=|s[j:k]|.

Now, vi is an ancestor of both vi and vy, i.e., s[:i] is a suffix of both s[:i] and s[:y]. Also, uy+1 is a descendant of uj,k, i.e., s[y+1:] has s[j:k] as prefix. As a consequence of these two facts, s[:i]s[j:k] and s[:y+t]=s[:y]s[y+1:y+t] share a common suffix of length i+t. But then, s[:x] (and, by extension, any match) must be a suffix (and consequently, a border) of s[:y+t]. In particular, s[:x] is the longest border of s[:y+t] of length at most i+t and we can determine x using Lemma 19.

Using Lemma 11, we obtain:

Corollary 24.

Given a string s, with |s|=n, we can construct data structures allowing us to retrieve PrefSubstrsc(i,j,k) for (i,j,k)PrefSubstrQueries(s) in

  • 𝒪(logδn) time for δ>0, using a 𝒪(n)-sized structure, or

  • 𝒪(loglogn) time, using a 𝒪(nloglogn)-sized structure.

5.2 Final algorithm

We will now present an algorithm to answer queries in 𝒪(1) time. We already have Lemma 20 for finding the length of the longest non-crossing match in constant time, so we again focus on crossing matches. In fact, let us subdivide PrefSubstrMatchessc(i,j,k) further:

Definition 25.

For string s, with |s|=n, (i,j,k)PrefSubstrQueries(s), and t:=|s[j:k]|=kj+1, we call a crossing match rPrefSubstrMatchessc(i,j,k) short, if and only if |r|2t and long, otherwise. We denote by

ShortPrefSubstrMatchessc(i,j,k):={rPrefSubstrMatchessc(i,j,k)|r|<2t}

and

LongPrefSubstrMatchessc(i,j,k):={rPrefSubstrMatchessc(i,j,k)|r|2t}

the set of short and long crossing matches respectively.

Short crossing matches.

As before, each match s[:x]ShortPrefSubstrMatchessc(i,j,k) corresponds to some s[:y] with y=xt<t that satisfies s[:y]Bord(s[:i]) and s[j:k]Pref(s[y+1:]). Previously, we have been checking, for each border s[:y], whether its posterior has prefix s[j:k], also making use of the structure that we found on borders. Now, we invert this procedure, we instead consider all suffixes s[z:] with zt that have prefix s[j:k] and then we check, for each of them, if their prior is a border of s[:i]. As it turns out, as for the borders of s[:i], the candidate suffixes also follow a structure that can be used to efficiently find our desired solution.

Lemma 26.

Let s be a string and let rSubstr(s) be a substring of s. Then

{zrPref(s[z:]),z|r|}

is an arithmetic progression and r is p-periodic with p being the step of the progression (if the progression has more than one element).

Proof.

Let t:=|r| and let z1<<z be the elements from the set. If there is only one element, we are done, so we assume 2 now. For k[1], define qk:=zk+1zkt1. Now for any k[1],

r[qk:]=s[zk+qk:zk+t1]=s[zk+1:zk+1qk+t1]=r[:tqk]

is a border or r, therefore r is qk-periodic. Let p:=per(r) with pqk for all k[1]. If =2, we have a progression with step q1 and we are done. Otherwise, we can choose k[1]{k}, and we have qk+pqk+qkk=11qk=zz1<|r|, by Lemma 1 we see that r is gcd(p,qk)-periodic which, due to minimality of p, implies that p divides qk. But now

s[zk:zk+qk+t1]=s[zk:zk+q1]s[zk+1:zk+1+t1]=r[:qk]r=(r[:p])qkpr

is also p-periodic and s[zk+p:zk+p+t1]=s[zk:zk+t1]=r, i.e, s[zk+p:] also has prefix r and it must be qk=p. As this holds for all k[1], we are done.

Now consider some node v in the suffix tree of s and some string rStrings(v)Substr(s) corresponding to that node. By Observation 8, the sets of suffixes starting with r are exactly the suffixes that correspond to descendants of v. Note that this includes a potential suffix corresponding to v itself, since by Lemma 9, the suffix has length at least |r|. As we can see, this set is independent from which rStrings(v)Substr(s) we choose. We can make use of this fact, combined with the constant size representation that we have seen above, to efficiently precompute the candidate suffixes s[z:].

Lemma 27.

For string s, with |s|=n, we can construct a linear-space data structure in 𝒪(n) time allowing us to determine a constant size representation of {zs[j:k]Pref(s[z:]),z|s[j:k]|} for any j,k[n],jk in 𝒪(1) time.

Proof.

During preprocessing, we first construct a suffix tree for s and some data structure allowing us to answer SuffNodes queries in constant time. Now we precompute the arithmetic progression {zrvPref(s[z:]),z|rv|} for each node v of the suffix tree, where rv is the longest element of Strings(v)Substr(s).

This precomputation is done bottom-up. Any leaf node of s corresponds to at most one suffix of s, so creating the progression is trivial for these nodes. For some inner node v with t:=|rv| and c children, we can compute the set in time 𝒪(c), as follows. First, we copy the progressions of each child node, removing all elements z>t in the process by pruning the length of the progression (note that the cutoff t gets smaller as we move up the tree, so we never have to add any elements back). Then we merge the (disjoint) progressions of the child node. This can be done efficiently, since there will only be at most two non-empty progressions in total, and at most one with more than a single element.

We can see this by considering the resulting progression {z,z+p,,z+(1)p} with some z,p,[n] and p<t (we assume 2, otherwise the claim is obviously true). For k{0}[2] we have

s[z+kp:z+kp+t]=s[z+kp:z+kp+t1]s[z+kp+t]=rvrv[tp+1]

since s[z+kp+t] is the tp+1-th character of s[z+(k+1)p:]. Therefore, all but the last suffix in the progression share a common prefix of length t+1 and this prefix belongs to some child of v. Consequently, the progression of this child contains all but (at most) one of the relevant suffixes.

Since each child has just a single parent, the total number of children (and similarly the required construction time) is 𝒪(n).

Now, when we receive a query j,k[n],jk, we determine the node v=SuffNodes(j,k) corresponding to s[j:k] and then return the arithmetic progression for v after we removed all elements larger than |s[j:k]|. It remains to show how to use the structure of the suffixes to quickly find the longest match:

Lemma 28.

For string s, with |s|=n, we can construct a linear-space data structure in 𝒪(n) time allowing us to determine the length of the longest element of ShortPrefSubstrMatchessc(i,j,k) for any (i,j,k)PrefSubstrQueries(s) in 𝒪(1) time.

Proof.

See Figure 2 for an illustration of the proof.

Figure 2: Sketch of the proof of Lemma 28. In the top part, we have the string s[:i]s[j:k] and its longest p-periodic suffix (marked with dots) of length d. This is aligned with the longest p-periodic suffix s[a:z+t1] of the =3 candidate matches s[:z+t1] for zZ in the bottom. In this case, the suffix of the middle candidate has length d.

In the preprocessing, we construct the data structure from Lemma 27, together with data structures for answering LCP queries on both s and sR in 𝒪(1) time (see Lemma 5).

When we receive a query (i,j,k)PrefSubstrQueries(s), we use the data structure from the previous lemma to determine {zs[j:k]Pref(s[z:]),zt} for t:=|s[j:k]|. We also remove from this progression all z>i+1, as the prior s[:z1] must be a border of s[:i]. Let Z={z0,z0+p,,z0+(1)p} be the resulting progression. If 2, we just check each element individually. In particular, s[:z1] is a suffix of s[:i], if and only if LCPs(1,iz+2)z1. Otherwise, we know that r:=s[j:k] is p-periodic and the same must hold for s[z0:z0+(1)p+t1]=(r[:p])1r. Similar to Lemma 21, where each posterior started with a different number of repetitions of some string, here we can see that the lengths of the longest p-periodic suffix of each potential match s[:z1+t] for zZ are pairwise distinct. This helps us to find the longest match, by comparing these lengths to the length of the longest p-periodic suffix of s[:i]s[j:k].

First, we determine, for all zZ, the longest p-periodic suffix of s[:z1+t]. This is done by determining the longest p-periodic suffix of s[:maxZ1+t] using an LCP query on sR, let s[a:maxZ1+t] be that suffix, az0. Now for any zZ, the longest p-periodic suffix of s[:z1+t] is s[a:z1+t]: obviously, it is p-periodic and s[a1:z1+t] cannot be p-periodic, otherwise s[a1]=s[a+p1] with a+p1<z0+t and s[a1:maxZ1+t] would also have to be p-periodic, which contradicts our assumption about a.

Next, we determine the length d of the longest p-periodic suffix of s[:i]s[j:k], it is dt. For this, we start by computing the length d of the longest common suffix of s[:i] and s[j:j+p1] via an LCP query on sR. If d<p, then s[id]s[j+pd1] and it is d=d+t. Otherwise, we find the length d′′ of the longest p-periodic suffix of s[:i]. Since the last p characters of s[:i] and the first p characters of s[j:k] are the same, we can extend this suffix by s[j:k] and obtain a p-periodic suffix of s[:i][j:k]. Consequently, d=d′′+t.

Comparing these lengths, we have to consider two different cases. First, we consider the case a=1, i.e., for every zZ, the entire candidate s[:z1+t] is p-periodic. If z1+td, then s[:z1+t] is exactly the (z1+t)-length suffix of s[:i]s[j:k], since both are p-periodic and share the last t>p characters. If z1+t>d, then it cannot be a suffix of s[:i]s[j:k] by maximality of d. Therefore, the largest zZ with z1+td corresponds to our solution. If a>1, and the longest p-periodic suffixes of s[:z1+t] and s[:i]s[j:k] do not match for some zZ, then the former cannot be a suffix of the latter. If they were, they would have to share a common p-periodic suffix of length min(z+ta,d)+1z1+ti+t, contradicting one of our assumptions about their longest p-periodic suffixes. Therefore, we find the zZ with z1+t=d and, if it exists, check if s[:z1+t] is a suffix of s[:i]s[j:k].

Long crossing matches.

Now, consider again the suffix tree and border tree. In Section 5.1 we have seen that for some s[:y] to induce a match for the query (i,j,k)PrefSubstrQueries(s), the SuffNodes(y+1,n) must be a descendant of SuffNodes(j,k) and the node corresponding to s[:y] must be an ancestor of the node corresponding to s[:i] in the border tree. We now label the nodes in the suffix tree in preorder, let y be the label assigned to SuffNodes(y+1,n). We also label, for each y[n], the node corresponding to s[:y] in the border tree with y.

Because we use preorder labels, the labels in any subtree of the suffix tree correspond to intervals. To find the longest match, we now need to determine the intersection of the interval corresponding to the subtree rooted in SuffNodes(j,k) and all labels corresponding to ancestors of s[:i] in the suffix tree. But the second set has size 𝒪(n), thus this is not directly possible to do in constant time. In the previous section, we saw how we can find short crossing matches. For longer matches we can make use of the following property:

Lemma 29.

Let s be a string of length n and let p[n1] with |Bordp(s)|2. Let {y0,y0+p,,y0+(1)p}={|r|rBordp(s)}. Then the posteriors s[y0+1:],s[y0+p+1:], ,s[y0+(2)p+1:] share a common prefix of length p.

Proof.

Due to Lemma 4, s[:y0+(1)p] is p-periodic and

s[y0+1:y0+p]=s[y0+p+1:y0+2p]==s[y0+(2)p+1:y0+(1)]

is a common prefix of length p.

This means that for p|s[j:k]| the posteriors of all but the longest element of Bordp(s[:i]) either all have prefix s[j:k], or none of them do. Therefore, since we are only interested in the longest border inducing a match, it suffices to only consider the labels of the longest two borders of Bordp(s[:i]). This allows us to make the set of labels small enough to achieve a constant query time, albeit using extra time during preprocessing:

Lemma 30.

For string s, with |s|=n, we can construct a data structure in 𝒪(nlogn) time and space allowing us to retrieve PrefSubstrsc(i,j,k) for any (i,j,k)PrefSubstrQueries(s) in 𝒪(1) time.

Proof.

During preprocessing, we construct the suffix tree and, since we want to exploit the periodicity of the borders, the compressed version of the border tree. We assign labels to both trees as described above, but we only assign label y to a node in the border tree, if s[:y] is one of the two largest borders corresponding to that node and maintain a set Y containing all such values of y. We also store, for each node of the suffix tree, the boundaries of the interval of labels for the subtree rooted in said node. For each node v in the compressed border tree, we create a fusion tree Lv containing all the labels y on the ancestors of the node and a fusion tree Pv containing the period of each ancestor (and, for each period value, a reference to the corresponding node). We also create a list Yv:={y[n]yLv}, sorted by the values of y and a data structure for answering RMQ-queries on this list (see Lemma 13). Since each node only has two labels and the compressed border tree has depth 𝒪(logn), both of these sets have size 𝒪(logn) as well. Since we need to do this for all 𝒪(n) nodes, we require a total of 𝒪(nlogn) time. We also construct the data structures from Lemmas 21 and 28.

When we receive a query (i,j,k)PrefSubstrQueries(s), we start by determining SuffNodes(j,k), the corresponding label interval [,+] and the node v corresponding to s[:i] in the border tree. By finding the successor y of and the predecessor y+ of + in Lv, we see that y and y+ are exactly the boundaries of the interval in Yv that contains values of y corresponding to a match, namely {y[n]yLv[,+]}. Using an RMQ-query, we can find the largest such y.

By now, we only have checked all y with per(s[:y])p. To check the rest, we start by finding the predecessor p of t:=|s[j:k]| in Pv and the corresponding node v (that has periodicity pt). We then check this node using Lemma 21 in constant time and move up the tree, checking each node we encounter, until we reach a node corresponding to borders of length at most t. This requires only a constant number of steps. Due to Lemma 2, the borders corresponding to v have length at most 2p2t and, in each step, the length of borders decreases by a factor of at least 32. The remaining borders can be checked using Lemma 28.

Reducing preprocessing time.

While we can now answer queries in constant time, this requires 𝒪(nlogn) preprocessing time. In particular, we have to create, for each of the 𝒪(n) nodes in the border tree, a data structure of size 𝒪(logn). In order to reduce this it seems futile to try and reduce the size of each data structure to be constant. Instead, we achieve a better preprocessing time by computing the structure only for a subset of nodes. For this, we resort to a micro-macro decomposition of the compressed border tree. By then constructing the structure from the previous section only on the macro-tree, we can answer the query (i,j,k)PrefSubstrQueries(s) if s[:i] corresponds to a special node. For a non-special node in some fragment T, its ancestors can be decomposed into two sets: the ancestors of the root node of T, which we can check in the same way, and the ancestors of the node in T. For the latter set, checking it can be reduced to a range query, which, due to the small size of T, turns out to be possible to answer in constant time.

Lemma 31.

For string s, with |s|=n, we can construct a linear-space data structure in 𝒪(n) time allowing us to retrieve PrefSubstrsc(i,j,k) for any (i,j,k)PrefSubstrQueries(s) in 𝒪(1) time.

Proof.

During preprocessing, we construct the data structure from Lemma 30, excluding the data structures specific to each node, i.e., Lv, Pv and Yv (including the structure for answering RMQ-queries on Yv). We then create a micro-macro decomposition of the compressed border tree, consisting of 𝒪(n/logn) fragments of size at most logn (see Lemma 12). For each of the special nodes, we create the 𝒪(logn)-sized data structure from Lemma 30.

For each fragment F of size m, we now construct the following data structure. First, we associate each y, where s[:y] corresponds to some node vy of F with a triple (py,p¯y,y), where py and p¯y are the preorder and postorder numbers of vy (in F, not the entire tree) and y is the preorder number of SuffNodes(y+1,n) in the suffix tree (same as in the proof of the previous lemma). We then create a fusion tree for YF={y1,,y2m} with y1<<y2m being the lengths of all the relevant prefixes in F, i.e., the longest two prefixes corresponding to each node of F. We also construct a fusion tree for LF={yyYF}. Next, we precompute the following sets:

  • {yYFpy[1,r]} for all r[m] (in ascending order),

  • {yYFp¯y[r,m]} for all r[m] (in descending order),

  • {yYFy[1,]} for all LF (in ascending order),

  • {yYFy[,n]} for all LF (in descending order).

Each of these sets can be encoded using a number w[n2] and thus, stored in two machine words, as follows. The set YF(w)YF encoded by w contains yr, if and only if the r-th least significant bit of w is set. If done in the order given above, each set can be constructed by adding one element to its predecessor, allowing for total 𝒪(m) construction time.

Hence, we construct data structures for each special node and fragment in 𝒪(logn) time. As there are at most 𝒪(n/logn) of those, constructing all data structures is done in 𝒪(n) time.

Given a query (i,j,k)PrefSubstrQueries(s), we determine SuffNodes(j,k), the corresponding label interval [,+] and the node vi corresponding to s[:i] in the border tree. We also find the fragment F containing vi and its root node ui. We can use the data structure from Lemma 30 on ui to determine the longest border corresponding to an ancestor of ui that induces a match. The remaining borders of s[:i] all correspond to ancestors of vi in the fragment, the lengths of the borders inducing a match is exactly

Yi:={yYF(py,p¯y,y)[1,pi]×[p¯i,m]×[,+]}.

The first two dimensions ensure that the node corresponding to s[:y] is an ancestor of vi and the last dimension ensures that the posterior has prefix s[j:k]. To determine Yi, we start by finding the minimum and maximum element min and max of LF[,+], which are the successor of and the predecessor of + in LF respectively. Yi is then the intersection of

  • {yYpy[1,pi]},

  • {yYp¯y[p¯i,m]},

  • {yYy[1,max]} and

  • {yYy[min,n]}.

Using bit operations, the bit representation of this intersection can be found in constant time and the same is true for the largest element in Yi, this corresponds to the most significant bit set in the representation.

As described in the overview, this last lemma completes the proof of Theorem 18:

Theorem 18. [Restated, see original statement.]

For string s, with |s|=n, we can construct a linear-space data structure in 𝒪(n) time allowing us to determine PrefSubstrs(i,j,k) for any (i,j,k)PrefSubstrQueries(s) in 𝒪(1) time.

References

  • [1] Paniz Abedin, Sahar Hooshmand, Arnab Ganguly, and Sharma V. Thankachan. The heaviest induced ancestors problem: Better data structures and applications. Algorithmica, 84(7):2088–2105, 2022. doi:10.1007/S00453-022-00955-7.
  • [2] Jarno N. Alanko, Davide Cenzato, Nicola Cotumaccio, Sung-Hwan Kim, Giovanni Manzini, and Nicola Prezza. Computing the LCP array of a labeled graph. In CPM, volume 296 of LIPIcs, pages 1:1–1:15. Schloss Dagstuhl – Leibniz-Zentrum für Informatik, 2024. doi:10.4230/LIPIcs.CPM.2024.1.
  • [3] Amihood Amir, Gary Benson, and Martin Farach. Let sleeping files lie: Pattern matching in z-compressed files. J. Comput. Syst. Sci., 52(2):299–307, 1996. doi:10.1006/JCSS.1996.0023.
  • [4] Rocco Ascone, Giulia Bernardini, Alessio Conte, Massimo Equi, Estéban Gabory, Roberto Grossi, and Nadia Pisanti. A unifying taxonomy of pattern matching in degenerate strings and founder graphs. In WABI, volume 312 of LIPIcs, pages 14:1–14:21. Schloss Dagstuhl – Leibniz-Zentrum für Informatik, 2024. doi:10.4230/LIPIcs.WABI.2024.14.
  • [5] Jasmijn A. Baaijens, Paola Bonizzoni, Christina Boucher, Gianluca Della Vedova, Yuri Pirola, Raffaella Rizzi, and Jouni Sirén. Computational graph pangenomics: a tutorial on data structures and their applications. Nat. Comput., 21(1):81–108, 2022. doi:10.1007/S11047-022-09882-6.
  • [6] Djamal Belazzougui, Dmitry Kosolobov, Simon J. Puglisi, and Rajeev Raman. Weighted ancestors in suffix trees revisited. In CPM, volume 191 of LIPIcs, pages 8:1–8:15. Schloss Dagstuhl – Leibniz-Zentrum für Informatik, 2021. doi:10.4230/LIPIcs.CPM.2021.8.
  • [7] Michael A. Bender and Martin Farach-Colton. The LCA problem revisited. In LATIN, volume 1776 of Lecture Notes in Computer Science, pages 88–94. Springer, 2000. doi:10.1007/10719839_9.
  • [8] Massimo Equi, Roberto Grossi, Veli Mäkinen, and Alexandru I. Tomescu. On the complexity of string matching for graphs. In ICALP, volume 132 of LIPIcs, pages 55:1–55:15. Schloss Dagstuhl – Leibniz-Zentrum für Informatik, 2019. doi:10.4230/LIPIcs.ICALP.2019.55.
  • [9] Martin Farach. Optimal suffix tree construction with large alphabets. In FOCS, pages 137–143. IEEE Computer Society, 1997. doi:10.1109/SFCS.1997.646102.
  • [10] Martin Farach and Mikkel Thorup. String matching in Lempel-Ziv compressed strings. Algorithmica, 20(4):388–404, 1998. doi:10.1007/PL00009202.
  • [11] Greg N. Frederickson. A data structure for dynamically maintaining rooted trees. J. Algorithms, 24(1):37–65, 1997. doi:10.1006/JAGM.1996.0835.
  • [12] 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.
  • [13] Moses Ganardi and Pawel Gawrychowski. Pattern matching on grammar-compressed strings in linear time. In SODA, pages 2833–2846. SIAM, 2022. doi:10.1137/1.9781611977073.110.
  • [14] Pawel Gawrychowski. Pattern matching in Lempel-Ziv compressed strings: Fast, simple, and deterministic. In ESA, volume 6942 of Lecture Notes in Computer Science, pages 421–432. Springer, 2011. doi:10.1007/978-3-642-23719-5_36.
  • [15] Pawel Gawrychowski. Optimal pattern matching in LZW compressed strings. ACM Trans. Algorithms, 9(3):25:1–25:17, 2013. doi:10.1145/2483699.2483705.
  • [16] Ming Gu, Martin Farach, and Richard Beigel. An efficient algorithm for dynamic text indexing. In SODA, pages 697–704. ACM/SIAM, 1994. URL: http://dl.acm.org/citation.cfm?id=314464.314675.
  • [17] Toru Kasai, Gunho Lee, Hiroki Arimura, Setsuo Arikawa, and Kunsoo Park. Linear-time longest-common-prefix computation in suffix arrays and its applications. In CPM, volume 2089 of Lecture Notes in Computer Science, pages 181–192. Springer, 2001. doi:10.1007/3-540-48194-X_17.
  • [18] M. Lothaire. Combinatorics on words, Second Edition. Cambridge mathematical library. Cambridge University Press, 1997.
  • [19] Solon P. Pissis. Optimal prefix-suffix queries with applications. In SOSA, pages 166–171. SIAM, 2025. doi:10.1137/1.9781611978315.13.

Appendix A Proofs from Section 2

Lemma 2. [Restated, see original statement.]

Let s be a string of length n with p=per(s) and let m[n1] such that s[:m] is a border of s with per(s[:m])=qp. Then p12m and n32m.

Proof.

We start by showing that pm2. If pm we are done. Otherwise, s[:m] as a substring of s must also be p-periodic and q<p. Now if p+q<m, then by Lemma 1, s[:m] would be gcd(p,q) periodic and q would have to divide p. But then s[:p] (which is q-periodic itself), can be written as a repetition of s[:q] and s would be q-periodic as well. Therefore, we have mp+q<2p.

The longest border of s (that is not s itself) has length np, i.e., nm+p32m.

Lemma 3. [Restated, see original statement.]

Let s be a string of length n. Then |{per(r)rBord(s)}|𝒪(logn).

Proof.

Let r1,,rkBord(s) be borders with 0<|ri|<|rj| and per(ri)per(rj) for all i,j[k],i<j. Then by the previous lemma, we have n|rk|32|rk1|(32)k1|r1|, i.e., k𝒪(logn).

Lemma 4. [Restated, see original statement.]

For string s, with |s|=n, and integer p[n1], the elements of {|r|rBordp(s)} form an arithmetic progression with step p. For p=, Bordp(s) contains at most one non-empty string.

Proof.

Note that a substring s of a periodic string s has per(s)per(s). In the case of the borders, this means that if we sort the borders by periodicity, the periodicity values will also be sorted. Consequently, the group of borders with the same periodicity q occur in a sequence and, since the largest border (that is not itself) of some q-periodic string is exactly q shorter, the lengths of the group in the border form an arithmetic progression with step q.

For p=, if there were two aperiodic borders r,rBord(s) with 0<|r|<|r|, then r would also be a border of r and r would have period q=|r||r| with 1q<|r|, which is a contradiction.

Lemma 9. [Restated, see original statement.]

Let s be a string, let rSuff(s) be a suffix of s and v be the node in the suffix tree of s corresponding to r. Then |r||r| for all rStrings(v)Substr(s) (this excludes all strings ending with $) and r is the only suffix of s corresponding to v.

Proof.

Assume we had rStrings(v)Substr(s) with |r|>|r|. By applying Observation 8 twice, we see that r$ must correspond to a descendant of v, and, since |r||r$|, r$ must be a prefix of r, which is a contradiction. The second part follows since the suffixes of s have pairwise different lengths.

Lemma 12. [Restated, see original statement.]

Given a tree of size n and some z[n], it is possible to find a micro-macro decomposition that has 𝒪(n/z) fragments of size at most z in 𝒪(n) time.

Proof.

We compute the fragments bottom-up the tree. For each node u, we maintain the number Fu of its descendants (including itself) that are not already part of some finished fragment and make sure that Fu does not grow larger than z. Initially, set Fu=1 for all leaf nodes u.

For some inner node u let Vu be the set of its children. Partition this into singleton sets, i.e., 𝒱:={{v}vVu}. Maintain a set of all small partitions V𝒱 with vVFv<z/2. Merge the partitions by repeatedly combining two small partitions until there is at most one small partition left. We now have a set of partitions each smaller than z. If there is only one partition V, we set Fu=1+vVFv. If there is more than one partition, we make u a special node and create a fragment for each of the partitions. Since other fragments will not contain any descendant of u, we set Fu=0. Note that we created k2 fragments and at least k1 of those contain at least z/2 non-special nodes. Therefore, our fragments contain on average Ω(z) non-special nodes and we cannot construct more than 𝒪(n/z) fragments.

Appendix B Proofs from Section 3

Lemma 16. [Restated, see original statement.]

For any string s and any query (i,j,k)PrefSubstrQueries(s), the answer x=PrefSubstrs(i,j,k) satisfies PrefSubstrMatchess(i,j,k)=Bord(s[:x]).

Proof.

For any element s[:y]PrefSubstrMatchess(i,j,k) we have |s[:y]||s[:x]| and s[:y],s[:x]Pref(s),Suff(s[:i][j:k]). It follows that s[:y] must both be a prefix and a suffix of s[:x], i.e., a border.

Any border b of s[:x] is both a prefix and a suffix of s[:x], therefore, by transitivity, both a prefix of s and a suffix of s[:i][j:k], i.e., bPrefSubstrMatchess(i,j,k).

Appendix C Crossing matches: Naive algorithm

Lemma 32.

Given a string s of length n, we can construct a linear-space data structure in 𝒪(n) time allowing us to determine PrefSubstrsc(i,j,k) for any query (i,j,k)PrefSubstrQueries(s) in 𝒪(n) time.

Proof.

During preprocessing, we construct a border tree for s and a data structure allowing us to answer LCP queries on s in constant time (see Lemma 5).

To answer a query (i,j,k), we obtain all the borders of s[:i] from the border tree (these correspond to ancestors of the node corresponding to s[:i]) and then check, for each border s[:y], whether its posterior has s[j:k] as a prefix. This is the case, if and only if LCPs(y+1,j)|s[j:k]|. Since there are up to i𝒪(n) borders of s[:i], processing queries still requires linear time.

Appendix D Proofs from Section 4

Lemma 19. [Restated, see original statement.]

Given string s, with |s|=n, we can construct a linear-space data structure in 𝒪(n) time allowing us to determine max{|r|rBord(s[:k]),|r|} for any k,[n] in 𝒪(1) time.

Proof.

During preprocessing, we construct a suffix tree for sR. For each suffix of sR, we visit the corresponding node and mark it as a suffix node; note that each suffix corresponds to a different node as seen in Lemma 9.

We then traverse the suffix tree in preorder, maintaining a stack of suffix nodes on the path from the root to the current node we visit. Using this, we can, for each node, store a reference to its lowest ancestor that is a suffix node. We also construct the data structure that allows to answer SuffNodesR queries (see Lemma 7).

When given a query (k,)[n]2, we determine v=SuffNodesR(nk+1,nk+), i.e., the node corresponding to (s[k+1:k])R in the suffix tree. Any s[:x]Bord(s[:k]) is a suffix of s[:k], therefore, (s[:x])R is a prefix of (s[k+1:k])R and a suffix of sR, and the corresponding node u must be an ancestor of v (by Observation 8) and a suffix node. Since we want to find the maximal value for x, we take the lowest such ancestor u and the corresponding suffix s[:x] is our answer (if u=v and the corresponding suffix is longer than , we use the lowest suffix node ancestor of the parent of v instead). If there exists no such suffix node, we return zero instead.

Appendix E Proofs from Section 5

Lemma 21. [Restated, see original statement.]

Let s be a string of length n. We can construct a linear-space data structure in 𝒪(n) time allowing us to find the largest y satisfying s[:y]Bordp(s[:i]) and s[j:k]Pref(s[y+1:]) for any query (i,j,k)PrefSubstrQueries(s) and periodicity p[i1]{} in constant time, provided we are given (an 𝒪(1) size representation of) Bordp(s[:i]) as well.

Proof.

During preprocessing, we construct a data structure allowing us to answer LCP queries on s in constant time (see Lemma 5).

If p=, then there is only a constant number of borders, which we can check individually as seen in the previous section. Therefore, we focus on the case p<.

The lengths of borders form an arithmetic progression, i.e., there are y0, such that Bord(s[:i])={s[:y0+p],s[:y0+2p],,s[:y0+p]}. Due to periodicity, it is

r:=s[y0+1:y0+p]=s[y0+p+1:y0+2p]==s[y0+(1)p+1:y0+p]

and, for some b[], we can write s[:y0+bp]=s[:y0]rb and s[y0+bp+1:]=rbs[y0+p+1:]

Obviously, each of the posteriors starts with some repetition of r and, for each of them, r is repeated a different number of times. If we consider both s[y0+1:] and s[y0+p+1:], we can see that the former must start with more repetitions of r than the latter. Therefore, by computing LCPs(y0+1,y0+p+1) we find the first mismatch between s[y0+1:] and s[y0+p+1:]. This tells us how many repetitions of r are at the start of s[y0+p+1:] and, consequently, how many there are at the start of each of the posteriors. By comparing this number with the number of repetitions of r at the start of s[j:k], we can quickly decide which borders might induce a match. This is explained in detail now.

We can determine the number of repetitions at the start of s[j:k] as follows. Let us say that, as determined previously, s[y0+1:]=rs[y0+p+1] starts with repetitions of r. We now calculate t=LCPs(y0+1,j).

If |s[j:k]|t and |s[j:k]|p, then s[j:k] consists only of repetitions of r. Therefore, any of the borders whose posterior starts with at least |s[j:k]|/p repetitions of r induces a match and, if there is a posterior starting with |s[j:k]|/p repetitions, the corresponding prior might induce a match (this can quickly be verified with another LCP query).

If pt (and p|s[j:k]|), then we know that s[j:k] starts with at least repetitions of r. Since all the posteriors start with fewer repetitions of r, there is no match in this case.

Otherwise, i.e., if t<p and t<|s[j:k]|, we now that s[j:k] starts with exactly t/p repetitions of r. The only candidate for inducing a match is the border, whose posterior starts with exactly that amount of repetitions of r (verified via LCP).

Lemma 22. [Restated, see original statement.]

For string s, with |s|=n, we can construct a linear-space data structure in 𝒪(n) time allowing us to retrieve PrefSubstrsc(i,j,k) for (i,j,k)PrefSubstrQueries(s) in 𝒪(logn) time.

Proof.

During preprocessing, we construct a compressed border tree for s and a data structure allowing us to answer LCP queries on s in constant time (see Lemma 5).

Given a query (i,j,k)PrefSubstrQueries(s), we start by locating the node corresponding to s[..i] in the border tree. By moving up the tree, we can obtain representations of Bordp(s[:i]) for all relevant values of p and check each of them using Lemma 21 in constant time. As soon as we find some border inducing a match, we return its length.